diff --git a/mozilla/netwerk/cache/src/Makefile.in b/mozilla/netwerk/cache/src/Makefile.in index 78e8294809c..ba492506f12 100644 --- a/mozilla/netwerk/cache/src/Makefile.in +++ b/mozilla/netwerk/cache/src/Makefile.in @@ -37,7 +37,6 @@ EXPORTS = \ $(NULL) CPPSRCS = \ - nsANSIFileStreams.cpp \ nsCache.cpp \ nsCacheEntry.cpp \ nsCacheEntryDescriptor.cpp \ @@ -45,6 +44,8 @@ CPPSRCS = \ nsCacheModule.cpp \ nsCacheService.cpp \ nsCacheSession.cpp \ + nsDiskCacheBinding.cpp \ + nsDiskCacheBlockFile.cpp \ nsDiskCacheDevice.cpp \ nsDiskCacheEntry.cpp \ nsDiskCacheMap.cpp \ diff --git a/mozilla/netwerk/cache/src/makefile.win b/mozilla/netwerk/cache/src/makefile.win index 503fba6f40c..b9649b9fea5 100755 --- a/mozilla/netwerk/cache/src/makefile.win +++ b/mozilla/netwerk/cache/src/makefile.win @@ -32,21 +32,22 @@ MAKE_OBJ_TYPE=DLL LCFLAGS = -DWIN32_LEAN_AND_MEAN -D_IMPL_NS_NET EXPORTS = \ - nsCacheService.h \ + nsCacheService.h \ $(NULL) CPP_OBJS = \ - .\$(OBJDIR)\nsCache.obj \ + .\$(OBJDIR)\nsCache.obj \ .\$(OBJDIR)\nsCacheEntry.obj \ - .\$(OBJDIR)\nsCacheEntryDescriptor.obj \ + .\$(OBJDIR)\nsCacheEntryDescriptor.obj \ .\$(OBJDIR)\nsCacheMetaData.obj \ .\$(OBJDIR)\nsCacheModule.obj \ .\$(OBJDIR)\nsCacheService.obj \ .\$(OBJDIR)\nsCacheSession.obj \ + .\$(OBJDIR)\nsDiskCacheBinding.obj \ + .\$(OBJDIR)\nsDiskCacheBlockFile.obj \ .\$(OBJDIR)\nsDiskCacheDevice.obj \ .\$(OBJDIR)\nsDiskCacheEntry.obj \ .\$(OBJDIR)\nsDiskCacheMap.obj \ - .\$(OBJDIR)\nsANSIFileStreams.obj \ .\$(OBJDIR)\nsMemoryCacheDevice.obj \ $(NULL) diff --git a/mozilla/netwerk/cache/src/nsCacheEntry.cpp b/mozilla/netwerk/cache/src/nsCacheEntry.cpp index 728074c3700..800e2941850 100644 --- a/mozilla/netwerk/cache/src/nsCacheEntry.cpp +++ b/mozilla/netwerk/cache/src/nsCacheEntry.cpp @@ -109,6 +109,25 @@ nsCacheEntry::~nsCacheEntry() } +nsresult +nsCacheEntry::Create( const char * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy, + nsCacheDevice * device, + nsCacheEntry ** result) +{ + nsCString* newKey = new nsCString(key); + if (!newKey) return NS_ERROR_OUT_OF_MEMORY; + + nsCacheEntry* entry = new nsCacheEntry(newKey, streamBased, storagePolicy); + if (!entry) { delete newKey; return NS_ERROR_OUT_OF_MEMORY; } + + entry->SetCacheDevice(device); + + *result = entry; + return NS_OK; +} + void nsCacheEntry::Fetched() { @@ -641,3 +660,4 @@ nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */, { ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = 0; } + diff --git a/mozilla/netwerk/cache/src/nsCacheEntry.h b/mozilla/netwerk/cache/src/nsCacheEntry.h index 6286bc6912e..efa558217af 100644 --- a/mozilla/netwerk/cache/src/nsCacheEntry.h +++ b/mozilla/netwerk/cache/src/nsCacheEntry.h @@ -55,6 +55,12 @@ public: ~nsCacheEntry(); + static nsresult Create( const char * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy, + nsCacheDevice * device, + nsCacheEntry ** result); + nsCString * Key() { return mKey; } PRInt32 FetchCount() { return mFetchCount;} diff --git a/mozilla/netwerk/cache/src/nsDiskCache.h b/mozilla/netwerk/cache/src/nsDiskCache.h index 0bc8fc672aa..05c0f9ad52e 100644 --- a/mozilla/netwerk/cache/src/nsDiskCache.h +++ b/mozilla/netwerk/cache/src/nsDiskCache.h @@ -26,174 +26,18 @@ #ifndef _nsDiskCache_h_ #define _nsDiskCache_h_ -#include "prtypes.h" -#include "prnetdb.h" -#include "nsDebug.h" +#include "nsCacheEntry.h" -class nsDiskCacheRecord { + +class nsDiskCache { +public: enum { - eEvictionRankMask = 0xFF000000, - eLocationSelectorMask = 0x00C00000, - - eExtraBlocksMask = 0x00300000, - eBlockNumberMask = 0x000FFFFF, - - eFileReservedMask = 0x003F0000, - eFileGenerationMask = 0x0000FFFF + kCurrentVersion = 0x00010003 // XXX whats the format? }; -public: - nsDiskCacheRecord() - : mHashNumber(0), mEvictionRank(0), mLocation(0), mUnused(0) - { - } - - PRUint32 HashNumber() const - { - return mHashNumber; - } - - void SetHashNumber(PRUint32 hashNumber) - { - mHashNumber = hashNumber; - } + enum { kData, kMetaData }; - PRUint32 EvictionRank() const - { - return mEvictionRank; - } - - void SetEvictionRank(PRUint32 rank) - { - mEvictionRank = rank; - } - - PRUint32 LocationSelector() const - { - return (PRUint32)(mLocation & eLocationSelectorMask) >> 22; - } - - void SetLocationSelector(PRUint32 selector) - { - mLocation &= ~eLocationSelectorMask; // clear location selector bits - mLocation |= (selector & eLocationSelectorMask) << 22; - } - - PRUint32 BlockCount() const - { - return (PRUint32)((mLocation & eExtraBlocksMask) >> 20) + 1; - } - - void SetBlockCount(PRUint32 count) - { - NS_ASSERTION( (count>=1) && (count<=4),"invalid block count"); - count = --count; - mLocation &= ~eExtraBlocksMask; // clear extra blocks bits - mLocation |= (count & eExtraBlocksMask) << 20; - } - - PRUint32 BlockNumber() const - { - return (mLocation & eBlockNumberMask); - } - - void SetBlockNumber(PRUint32 blockNumber) - { - mLocation &= ~eBlockNumberMask; // clear block number bits - mLocation |= blockNumber & eBlockNumberMask; - } - - PRUint16 FileGeneration() const - { - return (mLocation & eFileGenerationMask); - } - - void SetFileGeneration(PRUint16 generation) - { - mLocation &= ~eFileGenerationMask; // clear file generation bits - mLocation |= generation & eFileGenerationMask; - } - - void Swap() - { - mHashNumber = ::PR_htonl(mHashNumber); - mEvictionRank = ::PR_htonl(mEvictionRank); - mLocation = ::PR_htonl(mLocation); - mUnused = ::PR_htonl(mUnused); - } - - void Unswap() - { - mHashNumber = ::PR_ntohl(mHashNumber); - mEvictionRank = ::PR_ntohl(mEvictionRank); - mLocation = ::PR_ntohl(mLocation); - mUnused = ::PR_ntohl(mUnused); - } - -private: - PRUint32 mHashNumber; - PRUint32 mEvictionRank; - PRUint32 mLocation; - PRUint32 mUnused; + static PLDHashNumber Hash(const char* key); }; - -/* - -Cache Extent Table - - -1111 1111 0000 0000 0000 0000 0000 0000 : eEvictionRankMask -0000 0000 1100 0000 0000 0000 0000 0000 : eLocationSelectorMask (0-3) -0000 0000 0011 0000 0000 0000 0000 0000 : number of extra contiguous blocks 1-4 -0000 0000 0000 1111 1111 1111 1111 1111 : block# 0-1,048,575 - -0000 0000 0011 1111 0000 0000 0000 0000 : eFileReservedMask -0000 0000 0000 0000 1111 1111 1111 1111 : eFileGenerationMask - -0 file -1 256 bytes -2 1024 -3 4096 - - - log2 of seconds = 5 bits - -log2 minutes hours days years -00000 1 -00001 2 -00010 4 -00011 8 -00100 16 -00101 32 -00110 64 1 -00111 128 2 -01000 256 4 -01001 512 8.5 -01010 1024 17 -01011 2048 34 -01100 68 -01101 136 -01110 273 11 -01111 22 -10000 45.5 -10001 91 -10010 182 -10011 364 1 -10100 2 -10101 3 -10110 4 -10111 -11000 -11001 -11010 -11011 -11100 -11101 -11110 -11111 - -*/ - - #endif // _nsDiskCache_h_ diff --git a/mozilla/netwerk/cache/src/nsDiskCacheBinding.cpp b/mozilla/netwerk/cache/src/nsDiskCacheBinding.cpp new file mode 100644 index 00000000000..6449d170190 --- /dev/null +++ b/mozilla/netwerk/cache/src/nsDiskCacheBinding.cpp @@ -0,0 +1,348 @@ +/* -*- 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 nsDiskCacheBinding.cpp, released May 10, 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 + * Gordon Sheridan + */ + +#include + +#include "nsDiskCacheBinding.h" + + + +/****************************************************************************** + * static hash table callback functions + * + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark HASHTABLE CALLBACKS +#endif + +struct HashTableEntry : PLDHashEntryHdr { + nsDiskCacheBinding * mBinding; +}; + + +static const void * PR_CALLBACK +GetKey(PLDHashTable * /*table*/, PLDHashEntryHdr * header) +{ + return (void*) ((HashTableEntry *)header)->mBinding->mRecord.HashNumber(); +} + + +static PLDHashNumber PR_CALLBACK +HashKey( PLDHashTable *table, const void *key) +{ + return (PLDHashNumber) key; +} + + +static PRBool PR_CALLBACK +MatchEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr * header, + const void * key) +{ + HashTableEntry * hashEntry = (HashTableEntry *) header; + return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) key); +} + +static void PR_CALLBACK +MoveEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr * src, + PLDHashEntryHdr * dst) +{ + ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding; +} + + +static void PR_CALLBACK +ClearEntry(PLDHashTable * /* table */, + PLDHashEntryHdr * header) +{ + ((HashTableEntry *)header)->mBinding = nsnull; +} + + +/****************************************************************************** + * Utility Functions + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark DISK CACHE BINDERY +#endif + +nsDiskCacheBinding * +GetCacheEntryBinding(nsCacheEntry * entry) +{ + nsCOMPtr data; + nsresult rv = entry->GetData(getter_AddRefs(data)); + if (NS_FAILED(rv)) return nsnull; + + return (nsDiskCacheBinding *)data.get(); +} + + +/****************************************************************************** + * nsDiskCacheBinding + *****************************************************************************/ + +NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheBinding); + +nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record) + : mCacheEntry(entry) +{ + NS_ASSERTION(record->ValidRecord(), "bad record"); + NS_INIT_ISUPPORTS(); + PR_INIT_CLIST(this); + mRecord = *record; + mDoomed = entry->IsDoomed(); + mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files +} + +nsDiskCacheBinding::~nsDiskCacheBinding() +{ + NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list"); + if (!PR_CLIST_IS_EMPTY(this)) + PR_REMOVE_LINK(this); // XXX why are we still on a list? +} + + +/****************************************************************************** + * nsDiskCacheBindery + * + * Keeps track of bound disk cache entries to detect for collisions. + * + *****************************************************************************/ + +PLDHashTableOps nsDiskCacheBindery::ops = +{ + PL_DHashAllocTable, + PL_DHashFreeTable, + GetKey, + HashKey, + MatchEntry, + MoveEntry, + ClearEntry, + PL_DHashFinalizeStub +}; + + +nsDiskCacheBindery::nsDiskCacheBindery() + : initialized(PR_FALSE) +{ +} + + +nsDiskCacheBindery::~nsDiskCacheBindery() +{ + if (initialized) + PL_DHashTableFinish(&table); +} + + +nsresult +nsDiskCacheBindery::Init() +{ + nsresult rv = NS_OK; + initialized = PL_DHashTableInit(&table, &ops, nsnull, sizeof(HashTableEntry), 0); + + if (!initialized) rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} + + +nsDiskCacheBinding * +nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry, + nsDiskCacheRecord * record) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + nsCOMPtr data; + nsresult rv = entry->GetData(getter_AddRefs(data)); + if (NS_FAILED(rv) || data) { + NS_ASSERTION(!data, "cache entry already has bind data"); + return nsnull; + } + + nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record); + if (!binding) return nsnull; + + // give ownership of the binding to the entry + entry->SetData(binding); + + // add binding to collision detection system + rv = AddBinding(binding); + if (NS_FAILED(rv)) { + entry->SetData(nsnull); + return nsnull; + } + + return binding; +} + + +/** + * FindActiveEntry : to find active colliding entry so we can doom it + */ +nsDiskCacheBinding * +nsDiskCacheBindery::FindActiveBinding(PRUint32 hashNumber) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + // find hash entry for key + HashTableEntry * hashEntry; + hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, (void*) hashNumber, PL_DHASH_LOOKUP); + if (PL_DHASH_ENTRY_IS_FREE(hashEntry)) return nsnull; + + // walk list looking for active entry + NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding"); + nsDiskCacheBinding * binding = hashEntry->mBinding; + while (binding->mCacheEntry->IsDoomed()) { + binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + if (binding == hashEntry->mBinding) return nsnull; + } + return binding; +} + + +/** + * FindBinding : to identify whether a record is 'in use' so we don't evict it + */ +nsDiskCacheBinding * +nsDiskCacheBindery::FindBinding(nsDiskCacheRecord * record) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + // find hash entry for key + HashTableEntry * hashEntry; + hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, (void*) record->HashNumber(), PL_DHASH_LOOKUP); + if (PL_DHASH_ENTRY_IS_FREE(hashEntry)) return nsnull; + + // walk list looking for matching record (match on MetaLocation) + NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding"); + nsDiskCacheBinding * binding = hashEntry->mBinding; + while (binding->mRecord.MetaLocation() != record->MetaLocation()) { + binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + if (binding == hashEntry->mBinding) return nsnull; + } + return binding; +} + + + +/** + * AddBinding + * + * Called from FindEntry() if we read an entry off of disk + * - it may already have a generation number + * - a generation number conflict is an error + * + * Called from BindEntry() + * - a generation number needs to be assigned + */ +nsresult +nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding) +{ + NS_ENSURE_ARG_POINTER(binding); + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + + // find hash entry for key + HashTableEntry * hashEntry; + hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, + (void*) binding->mRecord.HashNumber(), + PL_DHASH_ADD); + if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY; + + if (hashEntry->mBinding == nsnull) { + hashEntry->mBinding = binding; + if (binding->mGeneration == 0) + binding->mGeneration = 1; // if generation uninitialized, set it to 1 + + return NS_OK; + } + + + // insert binding in generation order + nsDiskCacheBinding * p = hashEntry->mBinding; + PRBool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation? + if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized + while (1) { + + if (binding->mGeneration < p->mGeneration) { + // here we are + PR_INSERT_BEFORE(binding, p); + if (hashEntry->mBinding == p) + hashEntry->mBinding = binding; + } + + if (binding->mGeneration == p->mGeneration) { + if (calcGeneration) ++binding->mGeneration; // try the next generation + else { + NS_ASSERTION(binding->mGeneration != p->mGeneration, "generations collide!"); + return NS_ERROR_UNEXPECTED; + } + } + + p = (nsDiskCacheBinding *)PR_NEXT_LINK(p); + if (p == hashEntry->mBinding) { + // end of line: insert here or die + p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation + if (p->mGeneration == 255) { + NS_ASSERTION(p->mGeneration < 255, "generation capacity at full, the engines canna take it cap'n"); + return NS_ERROR_UNEXPECTED; + } + PR_INSERT_BEFORE(binding, hashEntry->mBinding); + } + } + return NS_OK; +} + + +/** + * RemoveBinding : remove binding from collision detection on deactivation + */ +void +nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + if (!initialized) return; + + HashTableEntry * hashEntry; + void * key = (void *)binding->mRecord.HashNumber(); + + hashEntry = (HashTableEntry*) PL_DHashTableOperate(&table, (void*) key, PL_DHASH_LOOKUP); + if (!PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { + NS_ASSERTION(PL_DHASH_ENTRY_IS_BUSY(hashEntry), "binding not in disk cache hashtable!"); + return; + } + + if (binding == hashEntry->mBinding) { + if (PR_CLIST_IS_EMPTY(binding)) { + // remove this hash entry + (void) PL_DHashTableOperate(&table, (void*) binding->mRecord.HashNumber(), PL_DHASH_REMOVE); + return; + + } else { + // promote next binding to head, and unlink this binding + hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + } + } + PR_REMOVE_AND_INIT_LINK(binding); +} diff --git a/mozilla/netwerk/cache/src/nsDiskCacheBinding.h b/mozilla/netwerk/cache/src/nsDiskCacheBinding.h new file mode 100644 index 00000000000..8d4a9a5663d --- /dev/null +++ b/mozilla/netwerk/cache/src/nsDiskCacheBinding.h @@ -0,0 +1,143 @@ +/* -*- 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 nsDiskCacheBinding.h, released May 10, 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): + * Gordon Sheridan + * Patrick C. Beard + */ + + +#ifndef _nsDiskCacheBinding_h_ +#define _nsDiskCacheBinding_h_ + +#include "nspr.h" +#include "pldhash.h" + +#include "nsISupports.h" +#include "nsCacheEntry.h" + +#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS +#include "nsITransport.h" +#endif + +#include "nsDiskCacheMap.h" + + +/****************************************************************************** + * nsDiskCacheBinding + * + * Created for disk cache specific data and stored in nsCacheEntry.mData as + * an nsISupports. Also stored in nsDiskCacheHashTable, with collisions + * linked by the PRCList. + * + *****************************************************************************/ + +class nsDiskCacheBinding : public nsISupports, public PRCList { +public: + NS_DECL_ISUPPORTS + + nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record); + virtual ~nsDiskCacheBinding(); + +#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS + /** + * Maps a cache access mode to a cached nsITransport for that access + * mode. We keep these cached to avoid repeated trips to the + * file transport service. + */ + nsCOMPtr& getTransport(nsCacheAccessMode mode) + { + return mTransports[mode - 1]; + } +#endif + + +// XXX make friends +public: + nsCacheEntry* mCacheEntry; // back pointer to parent nsCacheEntry + nsDiskCacheRecord mRecord; + PRBool mDoomed; // record is not stored in cache map + PRUint8 mGeneration; // possibly just reservation + +private: +#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS + nsCOMPtr mTransports[3]; +#endif +}; + + +/****************************************************************************** + * Utility Functions + *****************************************************************************/ + +nsDiskCacheBinding * GetCacheEntryBinding(nsCacheEntry * entry); + + + +/****************************************************************************** + * nsDiskCacheBindery + * + * Used to keep track of nsDiskCacheBinding associated with active/bound (and + * possibly doomed) entries. Lookups on 4 byte disk hash to find collisions + * (which need to be doomed, instead of just evicted. Collisions are linked + * using a PRCList to keep track of current generation number. + * + * Used to detect hash number collisions, and find available generation numbers. + * + * Not all nsDiskCacheBinding have a generation number. + * + * Generation numbers may be aquired late, or lost (when data fits in block file) + * + * Collisions can occur: + * BindEntry() - hashnumbers collide (possibly different keys) + * + * Generation number required: + * DeactivateEntry() - metadata written to disk, may require file + * GetFileForEntry() - force data to require file + * writing to stream - data size may require file + * + * Binding can be kept in PRCList in order of generation numbers. + * Binding with no generation number can be Appended to PRCList (last). + * + *****************************************************************************/ + +class nsDiskCacheBindery { +public: + nsDiskCacheBindery(); + ~nsDiskCacheBindery(); + + nsresult Init(); + + nsDiskCacheBinding * CreateBinding(nsCacheEntry * entry, + nsDiskCacheRecord * record); + + nsDiskCacheBinding * FindActiveBinding(PRUint32 hashNumber); + nsDiskCacheBinding * FindBinding(nsDiskCacheRecord * record); + nsresult AddBinding(nsDiskCacheBinding * binding); + void RemoveBinding(nsDiskCacheBinding * binding); + + +private: + // member variables + static PLDHashTableOps ops; + PLDHashTable table; + PRBool initialized; +}; + +#endif /* _nsDiskCacheBinding_h_ */ diff --git a/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.cpp b/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.cpp index d9cb5d8da24..f34f9755748 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.cpp +++ b/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.cpp @@ -35,19 +35,14 @@ nsresult nsDiskCacheBlockFile::Open( nsILocalFile * blockFile, PRUint32 blockSize) { - // create the stream + PRStatus err = PR_SUCCESS; + PRInt32 fileSize; + mBlockSize = blockSize; - mStream = new nsANSIFileStream; - if (!mStream) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(mStream); - // open the stream - nsresult rv = mStream->Open(blockFile); - if (NS_FAILED(rv)) { - NS_RELEASE(mStream); - mStream = nsnull; - return rv; - } + // open the file + nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00666, &mFD); + if (NS_FAILED(rv)) return rv; // unable to open or create file // allocate bit map buffer mBitMap = new PRUint8[kBitMapBytes]; @@ -57,17 +52,18 @@ nsDiskCacheBlockFile::Open( nsILocalFile * blockFile, PRUint32 blockSize) } // check if we just creating the file - rv = mStream->Available(&mEndOfFile); + fileSize = PR_Available(mFD); + if (fileSize < 0) { + // XXX an error occurred. We could call PR_GetError(), but how would that help? + rv = NS_ERROR_UNEXPECTED; + goto error_exit; + } + mEndOfFile = fileSize; if (mEndOfFile == 0) { // initialize bit map and write it nsCRT::zero(mBitMap, kBitMapBytes); - PRUint32 bytesWritten = 0; - rv = mStream->Write((char *)mBitMap, kBitMapBytes, &bytesWritten); - if (NS_FAILED(rv)) goto error_exit; - if (kBitMapBytes != bytesWritten) { - rv = NS_ERROR_UNEXPECTED; - goto error_exit; - } + PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes); + if (bytesWritten < kBitMapBytes) goto error_exit; mEndOfFile = kBitMapBytes; } else if (mEndOfFile < kBitMapBytes) { @@ -76,10 +72,8 @@ nsDiskCacheBlockFile::Open( nsILocalFile * blockFile, PRUint32 blockSize) } else { // read the bit map - PRUint32 bytesRead = 0; - rv = mStream->Read((char *)mBitMap, kBitMapBytes, &bytesRead); - if (NS_FAILED(rv)) goto error_exit; - if (kBitMapBytes != bytesRead) { + PRInt32 bytesRead = PR_Read(mFD, mBitMap, kBitMapBytes); + if (bytesRead < kBitMapBytes) { rv = NS_ERROR_UNEXPECTED; goto error_exit; } @@ -92,9 +86,9 @@ nsDiskCacheBlockFile::Open( nsILocalFile * blockFile, PRUint32 blockSize) return NS_OK; error_exit: - if (mStream) { - (void) mStream->Close(); - mStream = nsnull; + if (mFD) { + (void) PR_Close(mFD); + mFD = nsnull; } if (mBitMap) { @@ -111,19 +105,21 @@ error_exit: nsresult nsDiskCacheBlockFile::Close() { - if (!mStream) return NS_OK; + if (!mFD) return NS_OK; - nsresult rv = FlushBitMap(); - - nsresult rv2 = mStream->Close(); - NS_RELEASE(mStream); - mStream = nsnull; + nsresult rv = FlushBitMap(); + PRStatus err = PR_Close(mFD); + mFD = nsnull; if (mBitMap) { delete [] mBitMap; mBitMap = nsnull; } - return rv ? rv : rv2; + + if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS)) + rv = NS_ERROR_UNEXPECTED; + + return rv; } @@ -152,7 +148,7 @@ nsDiskCacheBlockFile::Trim() PRInt32 nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks) { - if (!mStream) return -1; // NS_ERROR_NOT_AVAILABLE; + if (!mFD) return -1; // NS_ERROR_NOT_AVAILABLE; // return -1 if unable to allocate blocks // PRUint8 mask = (0x01 << numBlocks) - 1; int i = 0; @@ -232,7 +228,7 @@ nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks) nsresult nsDiskCacheBlockFile::DeallocateBlocks( PRInt32 startBlock, PRInt32 numBlocks) { - if (!mStream) return NS_ERROR_NOT_AVAILABLE; + if (!mFD) return NS_ERROR_NOT_AVAILABLE; if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) || (numBlocks < 1) || (numBlocks > 4)) return NS_ERROR_ILLEGAL_VALUE; @@ -260,32 +256,30 @@ nsDiskCacheBlockFile::DeallocateBlocks( PRInt32 startBlock, PRInt32 numBlocks) * WriteBlocks *****************************************************************************/ nsresult -nsDiskCacheBlockFile::WriteBlocks( char * buffer, +nsDiskCacheBlockFile::WriteBlocks( void * buffer, PRInt32 startBlock, PRInt32 numBlocks) { // presume buffer != nsnull - if (!mStream) return NS_ERROR_NOT_AVAILABLE; + if (!mFD) return NS_ERROR_NOT_AVAILABLE; nsresult rv = VerifyAllocation(startBlock, numBlocks); if (NS_FAILED(rv)) return rv; // seek to block position PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize; - rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, blockPos); - if (NS_FAILED(rv)) return rv; + PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); + if (filePos != blockPos) return NS_ERROR_UNEXPECTED; if (mEndOfFile < (blockPos + numBlocks * mBlockSize)) mEndOfFile = (blockPos + numBlocks * mBlockSize); // write the blocks - PRUint32 bytesToWrite = numBlocks * mBlockSize; - PRUint32 bytesWritten = 0; - rv = mStream->Write(buffer, bytesToWrite, &bytesWritten); - if (NS_FAILED(rv)) return rv; - if (bytesWritten != bytesToWrite) return NS_ERROR_UNEXPECTED; + PRInt32 bytesToWrite = numBlocks * mBlockSize; + PRInt32 bytesWritten = PR_Write(mFD, buffer, bytesToWrite); + if (bytesWritten < bytesToWrite) return NS_ERROR_UNEXPECTED; // write the bit map and flush the file - rv = FlushBitMap(); + // XXX rv = FlushBitMap(); return rv; } @@ -294,26 +288,24 @@ nsDiskCacheBlockFile::WriteBlocks( char * buffer, * ReadBlocks *****************************************************************************/ nsresult -nsDiskCacheBlockFile::ReadBlocks( char * buffer, +nsDiskCacheBlockFile::ReadBlocks( void * buffer, PRInt32 startBlock, PRInt32 numBlocks) { // presume buffer != nsnull - if (!mStream) return NS_ERROR_NOT_AVAILABLE; + if (!mFD) return NS_ERROR_NOT_AVAILABLE; nsresult rv = VerifyAllocation(startBlock, numBlocks); if (NS_FAILED(rv)) return rv; // seek to block position PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize; - rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, blockPos); - if (NS_FAILED(rv)) return rv; + PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); + if (filePos != blockPos) return NS_ERROR_UNEXPECTED; // read the blocks - PRUint32 bytesToRead = numBlocks * mBlockSize; - PRUint32 bytesRead = 0; - rv = mStream->Read(buffer, bytesToRead, &bytesRead); - if (NS_FAILED(rv)) return rv; - if (bytesRead != bytesToRead) return NS_ERROR_UNEXPECTED; + PRInt32 bytesToRead = numBlocks * mBlockSize; + PRInt32 bytesRead = PR_Read(mFD, buffer, bytesToRead); + if (bytesRead < bytesToRead) return NS_ERROR_UNEXPECTED; return rv; } @@ -328,19 +320,15 @@ nsDiskCacheBlockFile::FlushBitMap() if (!mBitMapDirty) return NS_OK; // seek to bitmap - nsresult rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); - if (NS_FAILED(rv)) return rv; + PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET); + if (filePos != 0) return NS_ERROR_UNEXPECTED; // write bitmap - PRUint32 bytesWritten = 0; - rv = mStream->Write((char *)mBitMap, kBitMapBytes, &bytesWritten); - if (NS_FAILED(rv)) return rv; - if (kBitMapBytes != bytesWritten) { - return NS_ERROR_UNEXPECTED; - } - - rv = mStream->Flush(); - if (NS_FAILED(rv)) return rv; + PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes); + if (bytesWritten < kBitMapBytes) return NS_ERROR_UNEXPECTED; + + PRStatus err = PR_Sync(mFD); + if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED; mBitMapDirty = PR_FALSE; return NS_OK; @@ -356,20 +344,17 @@ nsDiskCacheBlockFile::FlushBitMap() nsresult nsDiskCacheBlockFile::ValidateFile() { - PRUint32 estimatedSize = kBitMapBytes; + PRInt32 estimatedSize = kBitMapBytes; PRInt32 lastBlock = LastBlock(); if (lastBlock >= 0) estimatedSize += (lastBlock + 1) * mBlockSize; - - PRUint32 fileSize = 0; - + // seek to beginning - nsresult rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); - if (NS_FAILED(rv)) return rv; - - rv = mStream->Available(&fileSize); - if (NS_FAILED(rv)) return rv; + PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET); + if (filePos != 0) return NS_ERROR_UNEXPECTED; + PRInt32 fileSize = PR_Available(mFD); + if (estimatedSize > fileSize) return NS_ERROR_UNEXPECTED; diff --git a/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.h b/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.h index c8b32b76455..5c6d6280c0e 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.h +++ b/mozilla/netwerk/cache/src/nsDiskCacheBlockFile.h @@ -24,18 +24,11 @@ #ifndef _nsDiskCacheBlockFile_h_ #define _nsDiskCacheBlockFile_h_ -#include "nsANSIFileStreams.h" +#include "nsILocalFile.h" +#include "nspr.h" enum { kBitMapBytes = 4096 }; -/* -typedef struct BlockFile { - PRUint32 version; // we could rely on the verion in the _CACHE_MAP_ file - PRUint32 blockSize; // caller can keep track of this - PRUint32 blockCount; // can be calculated & verified from bitmap and EOF - char bitMap[]; -} BlockFile; -*/ /****************************************************************************** * nsDiskCacheBlockFile @@ -48,7 +41,7 @@ typedef struct BlockFile { class nsDiskCacheBlockFile { public: nsDiskCacheBlockFile() - : mStream(nsnull) + : mFD(nsnull) , mBlockSize(0) , mEndOfFile(0) , mBitMap(nsnull) @@ -61,19 +54,19 @@ public: nsresult Trim(); PRInt32 AllocateBlocks( PRInt32 numBlocks); nsresult DeallocateBlocks( PRInt32 startBlock, PRInt32 numBlocks); - nsresult WriteBlocks( char * buffer, PRInt32 startBlock, PRInt32 numBlocks); - nsresult ReadBlocks( char * buffer, PRInt32 startBlock, PRInt32 numBlocks); + nsresult WriteBlocks( void * buffer, PRInt32 startBlock, PRInt32 numBlocks); + nsresult ReadBlocks( void * buffer, PRInt32 startBlock, PRInt32 numBlocks); -// private: - virtual nsresult FlushBitMap(); - virtual nsresult ValidateFile(); // called by Open() - virtual nsresult VerifyAllocation( PRInt32 startBlock, PRInt32 numBLocks); - virtual PRInt32 LastBlock(); +private: + nsresult FlushBitMap(); + nsresult ValidateFile(); // called by Open() + nsresult VerifyAllocation( PRInt32 startBlock, PRInt32 numBLocks); + PRInt32 LastBlock(); /** * Data members */ - nsANSIFileStream * mStream; + PRFileDesc * mFD; PRUint32 mBlockSize; PRUint32 mEndOfFile; PRUint8 * mBitMap; // XXX future: array of bit map blocks diff --git a/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp b/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp index 3e91bce908f..911db4cfa0c 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp +++ b/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp @@ -24,10 +24,11 @@ #include -#include "nsANSIFileStreams.h" #include "nsDiskCacheDevice.h" #include "nsDiskCacheEntry.h" #include "nsDiskCacheMap.h" +#include "nsDiskCache.h" + #include "nsCacheService.h" #include "nsCache.h" @@ -49,7 +50,13 @@ static nsresult ensureCacheDirectory(nsIFile * cacheDirectory); static const char DISK_CACHE_DEVICE_ID[] = { "disk" }; -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +/****************************************************************************** + * nsDiskCacheObserver + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark nsDiskCacheObserver +#endif // Using nsIPref will be subsumed with nsIDirectoryService when a selector // for the cache directory has been defined. @@ -252,23 +259,103 @@ static nsresult removeObservers(nsDiskCacheDevice* device) return NS_OK; } -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -static nsDiskCacheEntry* -ensureDiskCacheEntry(nsCacheEntry * entry) +/****************************************************************************** + * nsDiskCacheEvictor + * + * Helper class for nsDiskCacheDevice. + * + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark nsDiskCacheEvictor +#endif + +class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor { - nsCOMPtr data; - nsresult rv = entry->GetData(getter_AddRefs(data)); - if (NS_SUCCEEDED(rv) && !data) { - nsDiskCacheEntry* diskEntry = new nsDiskCacheEntry(entry); - data = diskEntry; - if (NS_SUCCEEDED(rv) && data) - entry->SetData(data.get()); +public: + nsDiskCacheEvictor( nsDiskCacheDevice * device, + nsDiskCacheMap * cacheMap, + nsDiskCacheBindery * cacheBindery, + PRInt32 targetSize, + const char * clientID) + : mDevice(device) + , mCacheMap(cacheMap) + , mBindery(cacheBindery) + , mTargetSize(targetSize) + , mClientID(clientID) + {} + + virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord); + +private: + nsDiskCacheDevice * mDevice; + nsDiskCacheMap * mCacheMap; + nsDiskCacheBindery * mBindery; + PRInt32 mTargetSize; + const char * mClientID; +}; + + +PRInt32 +nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord) +{ + // read disk cache entry + nsDiskCacheEntry * diskEntry = nsnull; + nsDiskCacheBinding * binding = nsnull; + char * clientID = nsnull; + PRInt32 result = kVisitNextRecord; + + if (mClientID) { + // we're just evicting records for a specific client + nsresult rv = mCacheMap->ReadDiskCacheEntry(mapRecord, &diskEntry); + if (NS_FAILED(rv)) goto exit; // XXX or delete record? + + // XXX FIXME compare clientID's without malloc + + // get client ID from key + rv = ClientIDFromCacheKey(nsLiteralCString(diskEntry->mKeyStart), &clientID); + if (NS_FAILED(rv)) goto exit; + + if (nsCRT::strcmp(mClientID, clientID) != 0) goto exit; } - return (nsDiskCacheEntry*) (nsISupports*) data.get(); + + if (mCacheMap->TotalSize() < mTargetSize) { + result = kStopVisitingRecords; + goto exit; + } + + binding = mBindery->FindActiveBinding(mapRecord->HashNumber()); + if (binding) { + // we are currently using this entry, so all we can do is doom it + + // since we're enumerating the records, we don't want to call DeleteRecord + // when nsCacheService::GlobalInstance()->DoomEntry_Locked() calls us back. + binding->mDoomed = PR_TRUE; // mark binding record as 'deleted' + nsCacheService::GlobalInstance()->DoomEntry_Locked(binding->mCacheEntry); + result = kDeleteRecordAndContinue; // this will REALLY delete the record + + } else { + // entry not in use, just delete storage because we're enumerating the records + (void) mCacheMap->DeleteStorage(mapRecord); + result = kDeleteRecordAndContinue; // this will REALLY delete the record + } + +exit: + + delete clientID; + delete diskEntry; + return result; } -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +/****************************************************************************** + * nsDiskCacheDeviceInfo + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark nsDiskCacheDeviceInfo +#endif class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo { public: @@ -286,25 +373,41 @@ public: private: nsDiskCacheDevice* mDevice; }; + NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo); /* readonly attribute string description; */ NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription) { NS_ENSURE_ARG_POINTER(aDescription); - char* result = nsCRT::strdup("Disk cache device"); - if (!result) return NS_ERROR_OUT_OF_MEMORY; - *aDescription = result; - return NS_OK; + *aDescription = nsCRT::strdup("Disk cache device"); + return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } /* readonly attribute string usageReport; */ -NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** aUsageReport) +NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport) { - NS_ENSURE_ARG_POINTER(aUsageReport); - char* result = nsCRT::strdup("disk cache usage report"); - if (!result) return NS_ERROR_OUT_OF_MEMORY; - *aUsageReport = result; + NS_ENSURE_ARG_POINTER(usageReport); + nsCString buffer; + + buffer.Assign("\n"); + + buffer.Append(""); + // buffer.Append(""); + buffer.Append("
Cache Directory: "); + nsCOMPtr cacheDir; + char * path; + mDevice->getCacheDirectory(getter_AddRefs(cacheDir)); + nsresult rv = cacheDir->GetPath(&path); + if (NS_SUCCEEDED(rv)) { + buffer.Append(path); + } else { + buffer.Append("directory unavailable"); + } + buffer.Append("
Files: XXX
"); + *usageReport = buffer.ToNewCString(); + if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; } @@ -332,286 +435,45 @@ NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize) return NS_OK; } -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -struct MetaDataHeader { - PRUint32 mHeaderSize; - PRInt32 mFetchCount; - PRUint32 mLastFetched; - PRUint32 mLastModified; - PRUint32 mExpirationTime; - PRUint32 mDataSize; - PRUint32 mKeySize; - PRUint32 mMetaDataSize; - // followed by null-terminated key and metadata string values. - - MetaDataHeader() - : mHeaderSize(sizeof(MetaDataHeader)), - mFetchCount(0), - mLastFetched(0), - mLastModified(0), - mExpirationTime(0), - mDataSize(0), - mKeySize(0), - mMetaDataSize(0) - { - } - - MetaDataHeader(nsCacheEntry* entry) - : mHeaderSize(sizeof(MetaDataHeader)), - mFetchCount(entry->FetchCount()), - mLastFetched(entry->LastFetched()), - mLastModified(entry->LastModified()), - mExpirationTime(entry->ExpirationTime()), - mDataSize(entry->DataSize()), - mKeySize(entry->Key()->Length() + 1), - mMetaDataSize(0) - { - } - - void Swap() - { - mHeaderSize = ::PR_htonl(mHeaderSize); - mFetchCount = ::PR_htonl(mFetchCount); - mLastFetched = ::PR_htonl(mLastFetched); - mLastModified = ::PR_htonl(mLastModified); - mExpirationTime = ::PR_htonl(mExpirationTime); - mDataSize = ::PR_htonl(mDataSize); - mKeySize = ::PR_htonl(mKeySize); - mMetaDataSize = ::PR_htonl(mMetaDataSize); - } - - void Unswap() - { - mHeaderSize = ::PR_ntohl(mHeaderSize); - mFetchCount = ::PR_ntohl(mFetchCount); - mLastFetched = ::PR_ntohl(mLastFetched); - mLastModified = ::PR_ntohl(mLastModified); - mExpirationTime = ::PR_ntohl(mExpirationTime); - mDataSize = ::PR_ntohl(mDataSize); - mKeySize = ::PR_ntohl(mKeySize); - mMetaDataSize = ::PR_ntohl(mMetaDataSize); - } -}; - -struct MetaDataFile : MetaDataHeader { - char* mKey; - char* mMetaData; - - MetaDataFile() - : mKey(nsnull), mMetaData(nsnull) - { - } - - MetaDataFile(nsCacheEntry* entry) - : MetaDataHeader(entry), - mKey(nsnull), mMetaData(nsnull) - { - } - - ~MetaDataFile() - { - delete[] mKey; - delete[] mMetaData; - } - - nsresult Init(nsCacheEntry* entry) - { - PRUint32 size = 1 + entry->Key()->Length(); - mKey = new char[size]; - if (!mKey) return NS_ERROR_OUT_OF_MEMORY; - nsCRT::memcpy(mKey, entry->Key()->get(), size); - return entry->FlattenMetaData(&mMetaData, &mMetaDataSize); - } - - nsresult Read(nsIInputStream* input); - nsresult Write(nsIOutputStream* output); -}; - -nsresult MetaDataFile::Read(nsIInputStream* input) -{ - nsresult rv; - PRUint32 count; - - // read in the file header. - rv = input->Read((char*)&mHeaderSize, sizeof(MetaDataHeader), &count); - if (NS_FAILED(rv)) return rv; - Unswap(); - - // make sure it is self-consistent. - if (mHeaderSize != sizeof(MetaDataHeader)) { - NS_ERROR("### CACHE FORMAT CHANGED!!! PLEASE DELETE YOUR NewCache DIRECTORY!!! ###"); - return NS_ERROR_ILLEGAL_VALUE; - } - - // read in the key. - delete[] mKey; - mKey = new char[mKeySize]; - if (!mKey) return NS_ERROR_OUT_OF_MEMORY; - rv = input->Read(mKey, mKeySize, &count); - if (NS_FAILED(rv)) return rv; - - // read in the metadata. - delete mMetaData; - mMetaData = nsnull; - if (mMetaDataSize) { - mMetaData = new char[mMetaDataSize]; - if (!mMetaData) return NS_ERROR_OUT_OF_MEMORY; - rv = input->Read(mMetaData, mMetaDataSize, &count); - if (NS_FAILED(rv)) return rv; - } - - return NS_OK; -} - -nsresult MetaDataFile::Write(nsIOutputStream* output) -{ - nsresult rv; - PRUint32 count; - - // write the header to the file. - Swap(); - rv = output->Write((char*)&mHeaderSize, sizeof(MetaDataHeader), &count); - Unswap(); - if (NS_FAILED(rv)) return rv; - - // write the key to the file. - rv = output->Write(mKey, mKeySize, &count); - if (NS_FAILED(rv)) return rv; - - // write the flattened metadata to the file. - if (mMetaDataSize) { - rv = output->Write(mMetaData, mMetaDataSize, &count); - if (NS_FAILED(rv)) return rv; - } - - return NS_OK; -} - -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -class nsDiskCacheEntryInfo : public nsICacheEntryInfo { -public: - NS_DECL_ISUPPORTS - NS_DECL_NSICACHEENTRYINFO - - nsDiskCacheEntryInfo() - { - NS_INIT_ISUPPORTS(); - } - - virtual ~nsDiskCacheEntryInfo() {} - - nsresult Read(nsIInputStream * input) - { - return mMetaDataFile.Read(input); - } - - const char* Key() { return mMetaDataFile.mKey; } - -private: - MetaDataFile mMetaDataFile; -}; -NS_IMPL_ISUPPORTS1(nsDiskCacheEntryInfo, nsICacheEntryInfo); - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetClientID(char ** clientID) -{ - NS_ENSURE_ARG_POINTER(clientID); - return ClientIDFromCacheKey(nsLiteralCString(mMetaDataFile.mKey), clientID); -} - - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetDeviceID(char ** deviceID) -{ - NS_ENSURE_ARG_POINTER(deviceID); - *deviceID = nsCRT::strdup(DISK_CACHE_DEVICE_ID); - return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; -} - - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetKey(char ** clientKey) -{ - NS_ENSURE_ARG_POINTER(clientKey); - return ClientKeyFromCacheKey(nsLiteralCString(mMetaDataFile.mKey), clientKey); -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetFetchCount(PRInt32 *aFetchCount) -{ - return *aFetchCount = mMetaDataFile.mFetchCount; - return NS_OK; -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched) -{ - *aLastFetched = mMetaDataFile.mLastFetched; - return NS_OK; -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastModified(PRUint32 *aLastModified) -{ - *aLastModified = mMetaDataFile.mLastModified; - return NS_OK; -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime) -{ - *aExpirationTime = mMetaDataFile.mExpirationTime; - return NS_OK; -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::IsStreamBased(PRBool *aStreamBased) -{ - *aStreamBased = PR_TRUE; - return NS_OK; -} - -NS_IMETHODIMP nsDiskCacheEntryInfo::GetDataSize(PRUint32 *aDataSize) -{ - *aDataSize = mMetaDataFile.mDataSize; - return NS_OK; -} - -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +/****************************************************************************** + * nsDiskCache + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark nsDiskCache +#endif /** - * Helper class for implementing do_QueryElementAt() pattern. Thanks scc! - * Eventually this should become part of nsICollection.idl. + * nsDiskCache::Hash(const char * key) + * + * This algorithm of this method implies nsDiskCacheRecords will be stored + * in a certain order on disk. If the algorithm changes, existing cache + * map files may become invalid, and therefore the kCurrentVersion needs + * to be revised. */ -class do_QueryElementAt : public nsCOMPtr_helper { -public: - do_QueryElementAt(nsICollection* aCollection, PRUint32 aIndex, nsresult* aErrorPtr = 0) - : mCollection(aCollection), - mIndex(aIndex), - mErrorPtr(aErrorPtr) - { - // nothing else to do here - } +PLDHashNumber +nsDiskCache::Hash(const char * key) +{ + PLDHashNumber h = 0; + for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s) + h = (h >> (PL_DHASH_BITS - 4)) ^ (h << 4) ^ *s; + return (h == 0 ? ULONG_MAX : h); +} - virtual nsresult operator()( const nsIID& aIID, void** aResult) const - { - nsresult status; - if ( mCollection ) { - if ( !NS_SUCCEEDED(status = mCollection->QueryElementAt(mIndex, aIID, aResult)) ) - *aResult = 0; - } else - status = NS_ERROR_NULL_POINTER; - if ( mErrorPtr ) - *mErrorPtr = status; - return status; - } - -private: - nsICollection* mCollection; - PRUint32 mIndex; - nsresult* mErrorPtr; -}; - -// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +/****************************************************************************** + * nsDiskCacheDevice + *****************************************************************************/ static nsCOMPtr gFileTransportService; +#ifdef XP_MAC +#pragma mark - +#pragma mark nsDiskCacheDevice +#endif + nsDiskCacheDevice::nsDiskCacheDevice() - : mInitialized(PR_FALSE), mCacheCapacity(0), mCacheMap(nsnull), mCacheStream(nsnull) + : mInitialized(PR_FALSE), mCacheCapacity(0), mCacheMap(nsnull) { } @@ -619,71 +481,82 @@ nsDiskCacheDevice::~nsDiskCacheDevice() { Shutdown(); delete mCacheMap; - NS_IF_RELEASE(mCacheStream); } + +/** + * methods of nsCacheDevice + */ nsresult nsDiskCacheDevice::Init() { nsresult rv; - rv = mBoundEntries.Init(); + rv = mBindery.Init(); if (NS_FAILED(rv)) return rv; - mCacheMap = new nsDiskCacheMap; - if (!mCacheMap) return NS_ERROR_OUT_OF_MEMORY; - // XXX examine preferences, and install observers to react to changes. rv = installObservers(this); if (NS_FAILED(rv)) return rv; - // XXX cache the file transport service to avoid repeated round trips - // through the service manager. + // hold the file transport service to avoid excessive calls to the service manager. gFileTransportService = do_GetService("@mozilla.org/network/file-transport-service;1", &rv); if (NS_FAILED(rv)) return rv; - + + // XXX are we sure we want to do this on startup? // delete "Cache.Trash" folder nsCOMPtr cacheTrashDir; - rv = mCacheDirectory->Clone(getter_AddRefs(cacheTrashDir)); - if (NS_FAILED(rv)) return rv; - rv = cacheTrashDir->SetLeafName("Cache.Trash"); - if (NS_FAILED(rv)) return rv; - (void) cacheTrashDir->Delete(PR_TRUE); // ignore errors, we tried... + rv = GetCacheTrashDirectory(getter_AddRefs(cacheTrashDir)); + if (NS_FAILED(rv)) goto error_exit; + (void) cacheTrashDir->Delete(PR_TRUE); // ignore errors, we tried... - // XXX read in persistent information about the cache. this can fail, if - // no cache directory has ever existed before. - rv = readCacheMap(); - if (NS_FAILED(rv) || mCacheMap->IsDirty()) { - rv = clobberDiskCache(); - if (NS_FAILED(rv)) return rv; + // Try opening cache map file. + mCacheMap = new nsDiskCacheMap; + if (!mCacheMap) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto error_exit; + } + + rv = mCacheMap->Open(mCacheDirectory); + if (NS_FAILED(rv)) { + rv = InitializeCacheDirectory(); // retry one time + if (NS_FAILED(rv)) goto error_exit; } - // XXX record that initialization succeeded. - mInitialized = PR_TRUE; + mInitialized = PR_TRUE; // record that initialization succeeded. return NS_OK; + +error_exit: + // XXX de-install observers? + if (mCacheMap) { + (void) mCacheMap->Close(); + delete mCacheMap; + mCacheMap = nsnull; + } + gFileTransportService = nsnull; + + return rv; } + nsresult nsDiskCacheDevice::Shutdown() { if (mInitialized) { - // XXX implement poor man's eviction strategy right here, - // keep deleting cache entries from oldest to newest, until - // cache usage is brought below limits. - evictDiskCacheEntries(); + // check cache limits in case we need to evict. + EvictDiskCacheEntries(); - // XXX write out persistent information about the cache. - mCacheMap->IsDirty() = PR_FALSE; - writeCacheMap(); + // write out persistent information about the cache. + (void) mCacheMap->Close(); - // XXX no longer initialized. + // no longer initialized. mInitialized = PR_FALSE; } - // XXX disconnect observers. + // disconnect observers. removeObservers(this); - // XXX release the reference to the cached file transport service. + // release the reference to the cached file transport service. gFileTransportService = nsnull; return NS_OK; @@ -713,147 +586,163 @@ nsDiskCacheDevice::GetDeviceID() } +/** + * FindEntry - + * + * cases: key not in disk cache, hash number free + * key not in disk cache, hash number used + * key in disk cache + */ nsCacheEntry * nsDiskCacheDevice::FindEntry(nsCString * key) { - // XXX look in entry hashtable first, if not found, then look on - // disk, to see if we have a disk cache entry that maches. - nsCacheEntry * entry = nsnull; - nsDiskCacheEntry * diskEntry = mBoundEntries.GetEntry(key->get()); - if (!diskEntry) { - PLDHashNumber hashNumber = nsDiskCacheEntry::Hash(key->get()); - nsDiskCacheRecord* record = mCacheMap->GetRecord(hashNumber); - if (record->HashNumber() == hashNumber) { - nsresult rv = readDiskCacheEntry(key->get(), &diskEntry); - if (NS_FAILED(rv)) return nsnull; - entry = diskEntry->getCacheEntry(); - rv = mBoundEntries.AddEntry(diskEntry); - if (NS_FAILED(rv)) { - delete entry; - return nsnull; - } - } - } else { - // XXX need to make sure this is an exact match, not just - // a hash code match. - entry = diskEntry->getCacheEntry(); - if (nsCRT::strcmp(entry->Key()->get(), key->get()) != 0) - return nsnull; + nsresult rv; + nsDiskCacheRecord record; + nsCacheEntry * entry = nsnull; + nsDiskCacheBinding * binding = nsnull; + PLDHashNumber hashNumber = nsDiskCache::Hash(key->get()); + +#if DEBUG /*because we shouldn't be called for active entries */ + binding = mBindery.FindActiveBinding(hashNumber); + NS_ASSERTION(!binding, "### FindEntry() called for a bound entry."); + binding = nsnull; +#endif + + // lookup hash number in cache map + rv = mCacheMap->FindRecord(hashNumber, &record); + if (NS_FAILED(rv)) return nsnull; // XXX log error? + + nsDiskCacheEntry * diskEntry; + rv = mCacheMap->ReadDiskCacheEntry(&record, &diskEntry); + if (NS_FAILED(rv)) goto error_exit; + + // compare key to be sure + if (nsCRT::strcmp(diskEntry->mKeyStart, key->get()) == 0) { + entry = diskEntry->CreateCacheEntry(this); } + delete diskEntry; + + binding = mBindery.CreateBinding(entry, &record); + if (!binding) { + goto error_exit; + } + return entry; + +error_exit: + delete entry; + return nsnull; } nsresult nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry) { - nsDiskCacheEntry* diskEntry = ensureDiskCacheEntry(entry); - if (mBoundEntries.GetEntry(diskEntry->getHashNumber()) == diskEntry) { - // XXX eventually, as a performance enhancement, keep entries around for a while before deleting them. - // XXX right now, to prove correctness, destroy the entries eagerly. - mBoundEntries.RemoveEntry(diskEntry); - } - - if (!entry->IsDoomed()) { - // commit any changes about this entry to disk. - updateDiskCacheEntry(diskEntry); + nsresult rv = NS_OK; + nsDiskCacheBinding * binding = GetCacheEntryBinding(entry); + NS_ASSERTION(binding, "DeactivateEntry: binding == nsnull"); + if (!binding) return NS_ERROR_UNEXPECTED; + + if (entry->IsDoomed()) { + // delete data, entry, record from disk for entry + rv = mCacheMap->DeleteStorage(&binding->mRecord); - // XXX if this entry collided with other concurrently bound entries, then its - // generation count will be non-zero. The other entries that came before it - // will be linked to it and doomed. deletion of the entry can only be done - // when all of the other doomed entries are deactivated, so that the final live entry - // can have its generation number reset to zero. - if (diskEntry->getGeneration() != 0) - scavengeDiskCacheEntries(diskEntry); - else - delete entry; } else { - // keep track of the cache total size. - mCacheMap->DataSize() -= entry->DataSize(); - - // obliterate all knowledge of this entry on disk. - deleteDiskCacheEntry(diskEntry); - - // XXX if this entry resides on a list, then there must have been a collision - // during the entry's lifetime. use this deactivation as a trigger to scavenge - // generation numbers, and reset the live entry's generation to zero. - if (!PR_CLIST_IS_EMPTY(diskEntry)) { - scavengeDiskCacheEntries(diskEntry); + // save stuff to disk for entry + rv = mCacheMap->WriteDiskCacheEntry(binding); + if (NS_FAILED(rv)) { + // clean up as best we can + (void) mCacheMap->DeleteRecordAndStorage(&binding->mRecord); + binding->mDoomed = PR_TRUE; // record is no longer in cache map } - - // delete entry from memory. - delete entry; } - return NS_OK; + mBindery.RemoveBinding(binding); // extract binding from collision detection stuff + delete entry; // which will release binding + return rv; } +/** + * BindEntry() + * no hash number collision -> no problem + * collision + * record not active -> evict, no problem + * record is active + * record is already doomed -> record shouldn't have been in map, no problem + * record is not doomed -> doom, and replace record in map + * + * walk matching hashnumber list to find lowest generation number + * take generation number from other (data/meta) location, + * or walk active list + */ nsresult -nsDiskCacheDevice::BindEntry(nsCacheEntry * newEntry) +nsDiskCacheDevice::BindEntry(nsCacheEntry * entry) { - nsresult rv; - - // Make sure this entry has its associated nsDiskCacheEntry data attached. - nsDiskCacheEntry* newDiskEntry = ensureDiskCacheEntry(newEntry); - NS_ASSERTION(newDiskEntry, "nsDiskCacheDevice::BindEntry"); - if (!newDiskEntry) return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = NS_OK; + nsDiskCacheRecord record, oldRecord; - // XXX check for cache collision. if an entry exists on disk that has the same - // hash code as this newly bound entry, AND there is already a bound entry for - // that key, we need to ask the cache service to doom that entry, since two - // simultaneous entries that have the same hash code aren't allowed until - // some sort of chaining mechanism is implemented. - nsDiskCacheEntry* oldDiskEntry = mBoundEntries.GetEntry(newEntry->Key()->get()); - if (oldDiskEntry) { - // XXX Hacky liveness test, remove when we've figured this all out. - if (oldDiskEntry->getRefCount() > 1) { - // set the generation count on the newly bound entry, - // so that files created will be unique and won't conflict - // with the doomed entries that are still active. - newDiskEntry->setGeneration(oldDiskEntry->getGeneration() + 1); - PR_APPEND_LINK(newDiskEntry, oldDiskEntry); - - // XXX Whom do we tell about this impending doom? - nsCacheEntry* oldEntry = oldDiskEntry->getCacheEntry(); - // XXX Yes Virginia, a doomed entry can be bound. - // NS_ASSERTION(!oldEntry->IsDoomed(), "a bound entry is doomed!"); - if (!oldEntry->IsDoomed()) - nsCacheService::GlobalInstance()->DoomEntry_Locked(oldEntry); - else - mBoundEntries.RemoveEntry(oldDiskEntry); - } else { - // XXX somehow we didn't hear about the entry going away. Ask gordon. - NS_NOTREACHED("bound disk cache entry with no corresponding cache entry."); - mBoundEntries.RemoveEntry(oldDiskEntry); + // create a new record for this entry + record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get())); + record.SetEvictionRank(ULONG_MAX - entry->ExpirationTime()); + + if (!entry->IsDoomed()) { + // if entry isn't doomed, add it to the cache map + rv = mCacheMap->AddRecord(&record, &oldRecord); // deletes old record, if any + if (NS_FAILED(rv)) return rv; + + PRUint32 oldHashNumber = oldRecord.HashNumber(); + if (oldHashNumber) { + // gotta evict this one first + nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber); + if (oldBinding) { + // XXX if debug : compare keys for hashNumber collision + + if (!oldBinding->mCacheEntry->IsDoomed()) { + // we've got a live one! + nsCacheService::GlobalInstance()->DoomEntry_Locked(oldBinding->mCacheEntry); + // storage will be delete when oldBinding->mCacheEntry is Deactivated + } + } else { + // delete storage + // XXX if debug : compare keys for hashNumber collision + rv = mCacheMap->DeleteStorage(&oldRecord); + if (NS_FAILED(rv)) return rv; // XXX delete record we just added? + } } } + + // Make sure this entry has its associated nsDiskCacheBinding attached. + nsDiskCacheBinding * binding = mBindery.CreateBinding(entry, &record); + NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry"); + if (!binding) return NS_ERROR_OUT_OF_MEMORY; + NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record"); - rv = mBoundEntries.AddEntry(newDiskEntry); - if (NS_FAILED(rv)) - return rv; +#if 0 + // XXX from old code: when would we bind an entry that already has data? PRUint32 dataSize = newEntry->DataSize(); if (dataSize) OnDataSizeChange(newEntry, dataSize); +#endif - // XXX Need to make this entry known to other entries? - // this probably isn't needed while the entry is bound, - // only when the entry is deactivated. this could be - // the reason disk cache performance suffers. - return updateDiskCacheEntry(newDiskEntry); + return NS_OK; } void nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry) { - // so it can't be seen by FindEntry() ever again. - nsDiskCacheEntry* diskEntry = ensureDiskCacheEntry(entry); - mBoundEntries.RemoveEntry(diskEntry); - - // XXX clear this entry out of the cache map. - // this is done in deleteDiskCacheEntry(). + nsDiskCacheBinding * binding = GetCacheEntryBinding(entry); + NS_ASSERTION(binding, "DoomEntry: binding == nsnull"); + if (!binding) return; + + if (!binding->mDoomed) { + // so it can't be seen by FindEntry() ever again. + nsresult rv = mCacheMap->DoomRecord(&binding->mRecord); + NS_ASSERTION(NS_SUCCEEDED(rv), "DoomRecord failed."); + binding->mDoomed = PR_TRUE; // record in no longer in cache map + } } @@ -865,57 +754,174 @@ nsDiskCacheDevice::GetTransportForEntry(nsCacheEntry * entry, NS_ENSURE_ARG_POINTER(entry); NS_ENSURE_ARG_POINTER(result); - nsDiskCacheEntry* diskEntry = ensureDiskCacheEntry(entry); - if (!diskEntry) return NS_ERROR_OUT_OF_MEMORY; + nsresult rv; + nsDiskCacheBinding * binding = GetCacheEntryBinding(entry); + NS_ASSERTION(binding, "GetTransportForEntry: binding == nsnull"); + if (!binding) return NS_ERROR_UNEXPECTED; + + NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other"); #ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS - nsCOMPtr& transport = diskEntry->getTransport(mode); + nsCOMPtr& transport = binding->getTransport(mode); if (transport) { NS_ADDREF(*result = transport); return NS_OK; } -#else - nsCOMPtr transport; #endif - - // XXX generate the name of the cache entry from the hash code of its key, - // modulo the number of files we're willing to keep cached. - nsCOMPtr dataFile; - nsresult rv = getFileForDiskCacheEntry(diskEntry, PR_FALSE, - getter_AddRefs(dataFile)); - if (NS_SUCCEEDED(rv)) { - rv = getTransportForFile(dataFile, mode, getter_AddRefs(transport)); - if (NS_SUCCEEDED(rv)) - NS_ADDREF(*result = transport); + + // check/set binding->mRecord for separate file, sync w/mCacheMap + if (binding->mRecord.DataLocationInitialized()) { + NS_ASSERTION(binding->mRecord.DataFile() == 0, "error: cache block file"); // make sure it's a separate file + NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync"); + } else { + binding->mRecord.SetDataFileGeneration(binding->mGeneration); + binding->mRecord.SetDataFileSize(0); // 1k minimum } + + if (!binding->mDoomed) { + // record stored in cache map, so update it + rv = mCacheMap->UpdateRecord(&binding->mRecord); + if (NS_FAILED(rv)) return rv; + } + + // generate the name of the cache entry from the hash code of its key, + // modulo the number of files we're willing to keep cached. + nsCOMPtr file; + rv = mCacheMap->GetFileForDiskCacheRecord(&binding->mRecord, + nsDiskCache::kData, + getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + + PRInt32 ioFlags = 0; + switch (mode) { + case nsICache::ACCESS_READ: + ioFlags = PR_RDONLY; + break; + case nsICache::ACCESS_WRITE: + ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + break; + case nsICache::ACCESS_READ_WRITE: + ioFlags = PR_RDWR | PR_CREATE_FILE; + break; + } + + rv = gFileTransportService->CreateTransport(file, ioFlags, PR_IRUSR | PR_IWUSR, result); return rv; } + nsresult nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry, nsIFile ** result) { - nsDiskCacheEntry* diskEntry = ensureDiskCacheEntry(entry); - if (!diskEntry) return NS_ERROR_OUT_OF_MEMORY; - return getFileForDiskCacheEntry(diskEntry, PR_FALSE, result); + nsresult rv; + nsDiskCacheBinding * binding = GetCacheEntryBinding(entry); + NS_ASSERTION(binding, "GetFileForEntry: binding == nsnull"); + if (!binding) return NS_ERROR_UNEXPECTED; + + // check/set binding->mRecord for separate file, sync w/mCacheMap + if (binding->mRecord.DataLocationInitialized()) { + NS_ASSERTION(binding->mRecord.DataFile() == 0, "error: cache block file"); // make sure it's a separate file + NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync"); + } else { + binding->mRecord.SetDataFileGeneration(binding->mGeneration); + binding->mRecord.SetDataFileSize(0); // 1k minimum + } + + if (!binding->mDoomed) { + // record stored in cache map, so update it + rv = mCacheMap->UpdateRecord(&binding->mRecord); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr file; + rv = mCacheMap->GetFileForDiskCacheRecord(&binding->mRecord, + nsDiskCache::kData, + getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + + NS_IF_ADDREF(*result = file); + return NS_OK; } + /** * This routine will get called every time an open descriptor is written to. */ nsresult nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize) { - PRUint32 newCacheSize = (mCacheMap->DataSize() += deltaSize); + nsDiskCacheBinding * binding = GetCacheEntryBinding(entry); + NS_ASSERTION(binding, "OnDataSizeChange: binding == nsnull"); + if (!binding) return NS_ERROR_UNEXPECTED; - if (newCacheSize > mCacheCapacity) { - // XXX go toss out some disk cache entries. - evictDiskCacheEntries(); - } + NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record"); + + PRUint32 sizeK = ((entry->DataSize() + 0x0399) >> 10); // round up to next 1k + PRUint32 newSizeK = ((entry->DataSize() + deltaSize + 0x399) >> 10); + + NS_ASSERTION((sizeK < USHRT_MAX) && (sizeK == binding->mRecord.DataFileSize()), + "data size out of sync"); + + mCacheMap->IncrementTotalSize((newSizeK - sizeK) * 1024); + newSizeK = (newSizeK < USHRT_MAX) ? newSizeK : USHRT_MAX; // record file size ceiling + binding->mRecord.SetDataFileSize(newSizeK); // update binding->mRecord + + EvictDiskCacheEntries(); return NS_OK; } + +/****************************************************************************** + * EntryInfoVisitor + *****************************************************************************/ +class EntryInfoVisitor : public nsDiskCacheRecordVisitor +{ +public: + EntryInfoVisitor(nsDiskCacheDevice * device, + nsDiskCacheMap * cacheMap, + nsICacheVisitor * visitor) + : mDevice(device) + , mCacheMap(cacheMap) + , mVisitor(visitor) + , mResult(NS_OK) + {} + + virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord) + { + // XXX optimization: do we have this record in memory? + + // read in the entry (metadata) + nsDiskCacheEntry * diskEntry; + nsresult rv = mCacheMap->ReadDiskCacheEntry(mapRecord, &diskEntry); + if (NS_FAILED(rv)) { + mResult = rv; + return kStopVisitingRecords; + } + + // create nsICacheEntryInfo + nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry); + if (!entryInfo) { + mResult = NS_ERROR_OUT_OF_MEMORY; + return kStopVisitingRecords; + } + nsCOMPtr ref(entryInfo); + + PRBool keepGoing; + rv = mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing); + delete diskEntry; + return keepGoing ? kVisitNextRecord : kStopVisitingRecords; + } + +private: + nsDiskCacheDevice * mDevice; + nsDiskCacheMap * mCacheMap; + nsICacheVisitor * mVisitor; + nsresult mResult; +}; + + nsresult nsDiskCacheDevice::Visit(nsICacheVisitor * visitor) { @@ -926,105 +932,116 @@ nsDiskCacheDevice::Visit(nsICacheVisitor * visitor) nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing); if (NS_FAILED(rv)) return rv; - if (keepGoing) - return visitEntries(visitor); + if (keepGoing) { + EntryInfoVisitor infoVisitor(this, mCacheMap, visitor); + return mCacheMap->VisitRecords(&infoVisitor); + } return NS_OK; } + nsresult nsDiskCacheDevice::EvictEntries(const char * clientID) { - nsresult rv; - - PRUint32 prefixLength = (clientID ? nsCRT::strlen(clientID) : 0); - PRUint32 newDataSize = mCacheMap->DataSize(); - PRUint32 newEntryCount = mCacheMap->EntryCount(); - - // XXX make sure meta data is up to date. - rv = updateDiskCacheEntries(); - if (NS_FAILED(rv)) return rv; - - nsDiskCacheRecord records[nsDiskCacheMap::kRecordsPerBucket]; - for (PRUint32 i = 0; i < nsDiskCacheMap::kBucketsPerTable; ++i) { - // XXX copy the i-th bucket from the cache map. GetBucket() - // should probably be changed to do this. - PRUint32 j, count = 0; - const nsDiskCacheRecord* bucket = mCacheMap->GetBucket(i); - for (j = 0; j < nsDiskCacheMap::kRecordsPerBucket; ++j) { - const nsDiskCacheRecord* record = bucket++; - if (record->HashNumber() == 0) - break; - records[count++] = *record; - } - for (j = 0; j < count; ++j) { - nsDiskCacheRecord* record = &records[j]; - - // if the entry is currently in use, then doom it rather than evicting right here. - nsDiskCacheEntry* diskEntry = mBoundEntries.GetEntry(record->HashNumber()); - if (diskEntry) { - nsCacheService::GlobalInstance()->DoomEntry_Locked(diskEntry->getCacheEntry()); - continue; - } - - // delete the metadata file. - nsCOMPtr metaFile; - rv = getFileForHashNumber(record->HashNumber(), PR_TRUE, record->FileGeneration(), getter_AddRefs(metaFile)); - if (NS_SUCCEEDED(rv)) { - if (clientID) { - // if filtering by clientID, make sure key prefix and clientID match. - // if anything fails, assume they don't by continuing with the loop. - nsCOMPtr input; - rv = openInputStream(metaFile, getter_AddRefs(input)); - if (NS_FAILED(rv)) continue; - - // read the metadata file. - MetaDataFile metaDataFile; - rv = metaDataFile.Read(input); - input->Close(); - if (NS_FAILED(rv)) continue; - - if (nsCRT::strncmp(clientID, metaDataFile.mKey, prefixLength) != 0) - continue; - } - rv = metaFile->Delete(PR_FALSE); - } - - PRUint32 dataSize = 0; - - // delete the data file - nsCOMPtr dataFile; - rv = getFileForHashNumber(record->HashNumber(), PR_FALSE, record->FileGeneration(), getter_AddRefs(dataFile)); - if (NS_SUCCEEDED(rv)) { - PRInt64 fileSize; - rv = dataFile->GetFileSize(&fileSize); - if (NS_SUCCEEDED(rv)) - LL_L2I(dataSize, fileSize); - rv = dataFile->Delete(PR_FALSE); - } - - // remove from cache map. - nsDiskCacheRecord* deletedRecord = mCacheMap->GetRecord(record->HashNumber()); - if (record->HashNumber() == deletedRecord->HashNumber() && record->FileGeneration() == deletedRecord->FileGeneration()) - mCacheMap->DeleteRecord(deletedRecord); - - // update the cache size/entry count. - if (newDataSize >= dataSize) newDataSize -= dataSize; - if (newEntryCount) --newEntryCount; - } - } - - if (clientID) { - mCacheMap->DataSize() = newDataSize; - mCacheMap->EntryCount() = newEntryCount; - } else { - mCacheMap->DataSize() = 0; - mCacheMap->EntryCount() = 0; - } - - return NS_OK; + nsDiskCacheEvictor evictor(this, mCacheMap, &mBindery, 0, clientID); + nsresult rv = mCacheMap->VisitRecords(&evictor); + return rv; } + +/** + * private methods + */ +#ifdef XP_MAC +#pragma mark - +#pragma mark PRIVATE METHODS +#endif + +nsresult +nsDiskCacheDevice::InitializeCacheDirectory() +{ + nsresult rv; + + // recursively delete the disk cache directory. + rv = mCacheDirectory->Delete(PR_TRUE); + if (NS_FAILED(rv)) { + // try moving it aside + + // create "Cache.Trash" directory if necessary + nsCOMPtr cacheTrashDir; + rv = GetCacheTrashDirectory(getter_AddRefs(cacheTrashDir)); + if (NS_FAILED(rv)) return rv; + + PRBool exists = PR_FALSE; + rv = cacheTrashDir->Exists(&exists); + if (NS_FAILED(rv)) return rv; + + if (!exists) { + // create the "Cache.Trash" directory + rv = cacheTrashDir->Create(nsIFile::DIRECTORY_TYPE,0777); + if (NS_FAILED(rv)) return rv; + } + + // create a directory with unique name to contain existing cache directory + rv = cacheTrashDir->Append("Cache"); + if (NS_FAILED(rv)) return rv; + rv = cacheTrashDir->CreateUnique(nsnull,nsIFile::DIRECTORY_TYPE, 0777); + if (NS_FAILED(rv)) return rv; + + // move existing cache directory into profileDir/Cache.Trash/CacheUnique + nsCOMPtr existingCacheDir; + rv = mCacheDirectory->Clone(getter_AddRefs(existingCacheDir)); + if (NS_FAILED(rv)) return rv; + rv = existingCacheDir->MoveTo(cacheTrashDir, nsnull); + if (NS_FAILED(rv)) return rv; + } + + rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777); + if (NS_FAILED(rv)) return rv; + + // reopen the cache map + rv = mCacheMap->Open(mCacheDirectory); + return rv; +} + + +nsresult +nsDiskCacheDevice::GetCacheTrashDirectory(nsIFile ** result) +{ + nsCOMPtr cacheTrashDir; + nsresult rv = mCacheDirectory->Clone(getter_AddRefs(cacheTrashDir)); + if (NS_FAILED(rv)) return rv; + rv = cacheTrashDir->SetLeafName("Cache.Trash"); + if (NS_FAILED(rv)) return rv; + + *result = cacheTrashDir.get(); + NS_ADDREF(*result); + return rv; +} + + +nsresult +nsDiskCacheDevice::EvictDiskCacheEntries() +{ + nsresult rv; + + if (mCacheMap->TotalSize() < mCacheCapacity) return NS_OK; + + nsDiskCacheEvictor evictor(this, mCacheMap, &mBindery, mCacheCapacity, nsnull); + rv = mCacheMap->EvictRecords(&evictor); + + return rv; +} + + +/** + * methods for prefs + */ +#ifdef XP_MAC +#pragma mark - +#pragma mark PREF METHODS +#endif void nsDiskCacheDevice::setPrefsObserver(nsIObserver* observer) { mPrefsObserver = observer; @@ -1040,14 +1057,22 @@ void nsDiskCacheDevice::setCacheDirectory(nsILocalFile* cacheDirectory) mCacheDirectory = cacheDirectory; } + +void +nsDiskCacheDevice::getCacheDirectory(nsILocalFile ** result) +{ + *result = mCacheDirectory; + NS_IF_ADDREF(*result); +} + + void nsDiskCacheDevice::setCacheCapacity(PRUint32 capacity) { mCacheCapacity = capacity; if (mInitialized) { - // XXX start evicting entries if the new size is smaller! + // start evicting entries if the new size is smaller! // XXX need to enter cache service lock here! - if (mCacheMap->DataSize() > capacity) - evictDiskCacheEntries(); + EvictDiskCacheEntries(); } } @@ -1058,717 +1083,10 @@ PRUint32 nsDiskCacheDevice::getCacheCapacity() PRUint32 nsDiskCacheDevice::getCacheSize() { - return mCacheMap->DataSize(); + return mCacheMap->TotalSize(); } PRUint32 nsDiskCacheDevice::getEntryCount() { return mCacheMap->EntryCount(); } - -nsresult nsDiskCacheDevice::getFileForHashNumber(PLDHashNumber hashNumber, PRBool meta, PRUint32 generation, nsIFile ** result) -{ - if (mCacheDirectory) { - nsCOMPtr entryFile; - nsresult rv = mCacheDirectory->Clone(getter_AddRefs(entryFile)); - if (NS_FAILED(rv)) - return rv; - // generate the hash code for this entry, and use that as a file name. - char name[32]; - ::sprintf(name, "%08X%c%02X", hashNumber, (meta ? 'm' : 'd'), generation); - entryFile->Append(name); - NS_ADDREF(*result = entryFile); - return NS_OK; - } - return NS_ERROR_NOT_AVAILABLE; -} -nsresult nsDiskCacheDevice::getFileForKey(const char* key, PRBool meta, - PRUint32 generation, nsIFile ** result) -{ - PLDHashNumber hash = nsDiskCacheEntry::Hash(key); - return getFileForHashNumber(hash, meta, generation, result); -} - -nsresult nsDiskCacheDevice::getFileForDiskCacheEntry(nsDiskCacheEntry * diskEntry, PRBool meta, - nsIFile ** result) -{ - return getFileForHashNumber(diskEntry->getHashNumber(), meta, diskEntry->getGeneration(), result); -} - -nsresult nsDiskCacheDevice::getTransportForFile(nsIFile* file, nsCacheAccessMode mode, nsITransport ** result) -{ - PRInt32 ioFlags = 0; - switch (mode) { - case nsICache::ACCESS_READ: - ioFlags = PR_RDONLY; - break; - case nsICache::ACCESS_WRITE: - ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; - break; - case nsICache::ACCESS_READ_WRITE: - ioFlags = PR_RDWR | PR_CREATE_FILE; - break; - } - return gFileTransportService->CreateTransport(file, ioFlags, PR_IRUSR | PR_IWUSR, result); -} - -nsresult nsDiskCacheDevice::openInputStream(nsIFile * file, nsIInputStream ** result) -{ - nsresult rv; - nsCOMPtr localFile = do_QueryInterface(file); - if (localFile) { - nsANSIInputStream* stream = new nsANSIInputStream; - if (!stream) return NS_ERROR_OUT_OF_MEMORY; - nsCOMPtr input(stream); - rv = stream->Open(localFile); - if (NS_FAILED(rv)) return rv; - NS_ADDREF(*result = input); - return NS_OK; - } else { - nsCOMPtr transport; - rv = getTransportForFile(file, nsICache::ACCESS_READ, getter_AddRefs(transport)); - if (NS_FAILED(rv)) return rv; - return transport->OpenInputStream(0, ULONG_MAX, 0, result); - } -} - -nsresult nsDiskCacheDevice::openOutputStream(nsIFile * file, nsIOutputStream ** result) -{ - nsresult rv; - nsCOMPtr localFile = do_QueryInterface(file); - if (localFile) { - nsANSIOutputStream* stream = new nsANSIOutputStream; - if (!stream) return NS_ERROR_OUT_OF_MEMORY; - nsCOMPtr output(stream); - rv = stream->Open(localFile); - if (NS_FAILED(rv)) return rv; - NS_ADDREF(*result = output); - return NS_OK; - } else { - nsCOMPtr transport; - rv = getTransportForFile(file, nsICache::ACCESS_WRITE, getter_AddRefs(transport)); - if (NS_FAILED(rv)) return rv; - return transport->OpenOutputStream(0, ULONG_MAX, 0, result); - } -} - -inline PRBool isMetaDataFile(const char* name) -{ - return (name[8] == 'm'); -} - -nsresult nsDiskCacheDevice::visitEntries(nsICacheVisitor * visitor) -{ - nsresult rv; - - // XXX make sure meta data is up to date. - rv = updateDiskCacheEntries(); - if (NS_FAILED(rv)) return rv; - - nsDiskCacheEntryInfo* entryInfo = new nsDiskCacheEntryInfo(); - if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY; - nsCOMPtr ref(entryInfo); - - for (PRUint32 i = 0; i < nsDiskCacheMap::kBucketsPerTable; ++i) { - nsDiskCacheRecord* bucket = mCacheMap->GetBucket(i); - for (PRUint32 j = 0; j < nsDiskCacheMap::kRecordsPerBucket; ++j) { - nsDiskCacheRecord* record = bucket++; - if (record->HashNumber() == 0) - break; - nsCOMPtr file; - rv = getFileForHashNumber(record->HashNumber(), PR_TRUE, record->FileGeneration(), getter_AddRefs(file)); - if (NS_FAILED(rv)) continue; - - nsCOMPtr input; - rv = openInputStream(file, getter_AddRefs(input)); - if (NS_FAILED(rv)) { - // delete non-existent record. - mCacheMap->DeleteRecord(record); - --bucket; - continue; - } - - // read the metadata file. - rv = entryInfo->Read(input); - input->Close(); - if (NS_FAILED(rv)) break; - - // tell the visitor about this entry. - PRBool keepGoing; - rv = visitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing); - if (NS_FAILED(rv)) return rv; - if (!keepGoing) break; - } - } - - return NS_OK; -} - -class UpdateEntryVisitor : public nsDiskCacheEntryHashTable::Visitor { - nsDiskCacheDevice* mDevice; -public: - UpdateEntryVisitor(nsDiskCacheDevice * device) : mDevice(device) {} - - virtual PRBool VisitEntry(nsDiskCacheEntry * diskEntry) - { - mDevice->updateDiskCacheEntry(diskEntry); - return PR_TRUE; - } -}; - -nsresult nsDiskCacheDevice::updateDiskCacheEntries() -{ - UpdateEntryVisitor visitor(this); - mBoundEntries.VisitEntries(&visitor); - return NS_OK; -} - -nsresult nsDiskCacheDevice::updateDiskCacheEntry(nsDiskCacheEntry* diskEntry) -{ - nsresult rv; - nsCacheEntry* entry = diskEntry->getCacheEntry(); - if (entry->IsMetaDataDirty() || entry->IsEntryDirty() || entry->IsDataDirty()) { - // make sure this disk entry is known to the cache map. - rv = updateCacheMap(diskEntry); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr file; - rv = getFileForDiskCacheEntry(diskEntry, PR_TRUE, - getter_AddRefs(file)); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr output; - rv = openOutputStream(file, getter_AddRefs(output)); - if (NS_FAILED(rv)) return rv; - - // write the metadata to the file. - MetaDataFile metaDataFile(entry); - rv = metaDataFile.Init(entry); - if (NS_FAILED(rv)) return rv; - rv = metaDataFile.Write(output); - output->Close(); - if (NS_FAILED(rv)) return rv; - - // mark the disk entry as being consistent with meta data file. - entry->MarkMetaDataClean(); - entry->MarkEntryClean(); - entry->MarkDataClean(); - } - return NS_OK; -} - -static nsresult NS_NewCacheEntry(nsCacheEntry ** result, - const char * key, - PRBool streamBased, - nsCacheStoragePolicy storagePolicy, - nsCacheDevice* device) -{ - nsCString* newKey = new nsCString(key); - if (!newKey) return NS_ERROR_OUT_OF_MEMORY; - - nsCacheEntry* entry = new nsCacheEntry(newKey, streamBased, storagePolicy); - if (!entry) { delete newKey; return NS_ERROR_OUT_OF_MEMORY; } - - entry->SetCacheDevice(device); - - *result = entry; - return NS_OK; -} - -nsresult nsDiskCacheDevice::readDiskCacheEntry(const char * key, nsDiskCacheEntry ** result) -{ - // result should be nsull on cache miss. - *result = nsnull; - - // up front, check to see if the file we are looking for exists. - nsCOMPtr file; - nsresult rv = getFileForKey(key, PR_TRUE, 0, getter_AddRefs(file)); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr input; - rv = openInputStream(file, getter_AddRefs(input)); - if (NS_FAILED(rv)) return rv; - - // read the metadata file. - MetaDataFile metaDataFile; - rv = metaDataFile.Read(input); - input->Close(); - if (NS_FAILED(rv)) return rv; - - // Ensure that the keys match. - if (nsCRT::strcmp(key, metaDataFile.mKey) != 0) return NS_ERROR_NOT_AVAILABLE; - - nsCacheEntry* entry = nsnull; - rv = NS_NewCacheEntry(&entry, key, PR_TRUE, nsICache::STORE_ON_DISK, this); - if (NS_FAILED(rv)) return rv; - - // initialize the entry. - entry->SetFetchCount(metaDataFile.mFetchCount); - entry->SetLastFetched(metaDataFile.mLastFetched); - entry->SetLastModified(metaDataFile.mLastModified); - entry->SetExpirationTime(metaDataFile.mExpirationTime); - entry->SetDataSize(metaDataFile.mDataSize); - - // restore the metadata. - if (metaDataFile.mMetaDataSize) { - rv = entry->UnflattenMetaData(metaDataFile.mMetaData, metaDataFile.mMetaDataSize); - if (NS_FAILED(rv)) goto error; - } - - // celebrate! - *result = ensureDiskCacheEntry(entry); - if (!*result) goto error; - return NS_OK; - -error: - // oh, auto_ptr<> would be nice right about now. - delete entry; - return NS_ERROR_NOT_AVAILABLE; -} - -nsresult nsDiskCacheDevice::deleteDiskCacheEntry(nsDiskCacheEntry * diskEntry) -{ - nsresult rv; - - // delete the metadata file. - nsCOMPtr metaFile; - rv = getFileForDiskCacheEntry(diskEntry, PR_TRUE, - getter_AddRefs(metaFile)); - if (NS_SUCCEEDED(rv)) { - rv = metaFile->Delete(PR_FALSE); - // NS_ASSERTION(NS_SUCCEEDED(rv), "nsDiskCacheDevice::deleteDiskCacheEntry"); - } - - // delete the data file - nsCOMPtr dataFile; - rv = getFileForDiskCacheEntry(diskEntry, PR_FALSE, - getter_AddRefs(dataFile)); - if (NS_SUCCEEDED(rv)) { - rv = dataFile->Delete(PR_FALSE); - // NS_ASSERTION(NS_SUCCEEDED(rv), "nsDiskCacheDevice::deleteDiskCacheEntry"); - } - - // remove from cache map. - nsDiskCacheRecord* record = mCacheMap->GetRecord(diskEntry->getHashNumber()); - if (record->HashNumber() == diskEntry->getHashNumber() && record->FileGeneration() == diskEntry->getGeneration()) - mCacheMap->DeleteRecord(record); - - return NS_OK; -} - -nsresult nsDiskCacheDevice::scavengeDiskCacheEntries(nsDiskCacheEntry * diskEntry) -{ - nsresult rv; - - // count the number of doomed entries still in the list, not - // including the passed-in entry. if this number is zero, then - // the liveEntry, if inactive, can have its generation reset - // to zero. - PRUint32 doomedEntryCount = 0; - nsDiskCacheEntry * youngestDiskEntry = diskEntry; - nsCacheEntry * youngestEntry = diskEntry->getCacheEntry(); - nsDiskCacheEntry * nextDiskEntry = NS_STATIC_CAST(nsDiskCacheEntry*, PR_NEXT_LINK(diskEntry)); - while (nextDiskEntry != diskEntry) { - nsCacheEntry* nextEntry = nextDiskEntry->getCacheEntry(); - if (nextEntry->IsDoomed()) { - ++doomedEntryCount; - } else if (nextDiskEntry->getGeneration() > youngestDiskEntry->getGeneration()) { - youngestEntry = nextEntry; - youngestDiskEntry = nextDiskEntry; - } - nextDiskEntry = NS_STATIC_CAST(nsDiskCacheEntry*, PR_NEXT_LINK(nextDiskEntry)); - } - - if (doomedEntryCount == 0 && !youngestEntry->IsDoomed() && !youngestEntry->IsActive()) { - PRUint32 generation = youngestDiskEntry->getGeneration(); - // XXX reset generation number. - const char* key = youngestEntry->Key()->get(); - nsCOMPtr oldFile, newFile; - nsXPIDLCString newName; - - // rename metadata file. - rv = getFileForKey(key, PR_TRUE, generation, getter_AddRefs(oldFile)); - if (NS_FAILED(rv)) return rv; - rv = getFileForKey(key, PR_TRUE, 0, getter_AddRefs(newFile)); - rv = newFile->GetLeafName(getter_Copies(newName)); - if (NS_FAILED(rv)) return rv; - rv = oldFile->MoveTo(nsnull, newName); - NS_ASSERTION(NS_SUCCEEDED(rv), "nsDiskCacheDevice::scavengeDiskCacheEntries"); - - // rename data file. - rv = getFileForKey(key, PR_FALSE, generation, getter_AddRefs(oldFile)); - if (NS_FAILED(rv)) return rv; - rv = getFileForKey(key, PR_FALSE, 0, getter_AddRefs(newFile)); - rv = newFile->GetLeafName(getter_Copies(newName)); - if (NS_FAILED(rv)) return rv; - rv = oldFile->MoveTo(nsnull, newName); - NS_ASSERTION(NS_SUCCEEDED(rv), "nsDiskCacheDevice::scavengeDiskCacheEntries"); - - // delete the youngestEntry, otherwise nobody else will. - delete youngestEntry; - } - - return NS_OK; -} - -nsresult nsDiskCacheDevice::scanDiskCacheEntries(nsISupportsArray ** result) -{ - nsresult rv; - - // XXX make sure meta data is up to date. - rv = updateDiskCacheEntries(); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr files; - rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(files)); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr entries = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); - if (NS_FAILED(rv)) return rv; - - PRUint32 newCacheSize = 0; - - for (PRBool more; NS_SUCCEEDED(files->HasMoreElements(&more)) && more;) { - nsCOMPtr next; - rv = files->GetNext(getter_AddRefs(next)); - if (NS_FAILED(rv)) break; - nsCOMPtr file(do_QueryInterface(next, &rv)); - if (NS_FAILED(rv)) break; - nsXPIDLCString name; - rv = file->GetLeafName(getter_Copies(name)); - if (isMetaDataFile(name)) { - // this must be a metadata file. - nsCOMPtr input; - rv = openInputStream(file, getter_AddRefs(input)); - if (NS_FAILED(rv)) continue; - - nsDiskCacheEntryInfo* entryInfo = new nsDiskCacheEntryInfo(); - if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY; - nsCOMPtr ref(entryInfo); - rv = entryInfo->Read(input); - input->Close(); - if (NS_FAILED(rv)) return rv; - - // update the cache size. - PRUint32 dataSize; - entryInfo->GetDataSize(&dataSize); - newCacheSize += dataSize; - - // update the cache map. - PLDHashNumber hashNumber = nsDiskCacheEntry::Hash(entryInfo->Key()); - nsDiskCacheRecord* record = mCacheMap->GetRecord(hashNumber); - record->SetHashNumber(hashNumber); - record->SetEvictionRank(0); - record->SetFileGeneration(0); - - // sort entries by modification time. - PRUint32 count; - entries->Count(&count); - if (count == 0) { - rv = entries->AppendElement(entryInfo); - if (NS_FAILED(rv)) return rv; - continue; - } - PRUint32 modTime; - entryInfo->GetLastModified(&modTime); - PRInt32 low = 0, high = count - 1; - for (;;) { - PRInt32 middle = (low + high) / 2; - nsCOMPtr info = do_QueryElementAt(entries, middle, &rv); - if (NS_FAILED(rv)) return rv; - PRUint32 modTime1; - info->GetLastModified(&modTime1); - if (low >= high) { - if (modTime <= modTime1) - rv = entries->InsertElementAt(entryInfo, middle); - else - rv = entries->InsertElementAt(entryInfo, middle + 1); - if (NS_FAILED(rv)) return rv; - break; - } else { - if (modTime < modTime1) { - high = middle - 1; - } else if (modTime > modTime1) { - low = middle + 1; - } else { - rv = entries->InsertElementAt(entryInfo, middle); - if (NS_FAILED(rv)) return rv; - break; - } - } - } - } - } - - NS_ADDREF(*result = entries); - - // we've successfully totaled the cache size. - mCacheMap->DataSize() = newCacheSize; - entries->Count(&mCacheMap->EntryCount()); - - return NS_OK; -} - - -nsresult nsDiskCacheDevice::clobberDiskCache() -{ - nsresult rv; - - // close the cache map file. - NS_IF_RELEASE(mCacheStream); - - // recursively delete the disk cache directory. - rv = mCacheDirectory->Delete(PR_TRUE); - if (NS_FAILED(rv)) { - // try moving it aside - - // get parent directory of cache directory - nsCOMPtr cacheParentDir; - rv = mCacheDirectory->GetParent(getter_AddRefs(cacheParentDir)); - if (NS_FAILED(rv)) return rv; - - // create "Cache.Trash" directory if necessary - nsCOMPtr oldCacheDir; - rv = cacheParentDir->Clone(getter_AddRefs(oldCacheDir)); - if (NS_FAILED(rv)) return rv; - rv = oldCacheDir->Append("Cache.Trash"); - if (NS_FAILED(rv)) return rv; - - PRBool exists = PR_FALSE; - rv = oldCacheDir->Exists(&exists); - if (NS_FAILED(rv)) return rv; - - if (!exists) { - // create the "Cache.Trash" directory - rv = oldCacheDir->Create(nsIFile::DIRECTORY_TYPE,0777); - if (NS_FAILED(rv)) return rv; - } - - // create a directory with unique name to contain existing cache directory - rv = oldCacheDir->Append("Cache"); - if (NS_FAILED(rv)) return rv; - rv = oldCacheDir->CreateUnique(nsnull,nsIFile::DIRECTORY_TYPE, 0777); - if (NS_FAILED(rv)) return rv; - - // move existing cache directory into profileDir/Cache.Trash/CacheUnique - nsCOMPtr existingCacheDir; - rv = mCacheDirectory->Clone(getter_AddRefs(existingCacheDir)); - if (NS_FAILED(rv)) return rv; - rv = existingCacheDir->MoveTo(oldCacheDir, nsnull); - if (NS_FAILED(rv)) return rv; - } - - rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777); - if (NS_FAILED(rv)) return rv; - - // reset the cache map - mCacheMap->Reset(); - - rv = openCacheMap(); - if (NS_FAILED(rv)) return rv; - - rv = mCacheMap->Write(mCacheStream); - return rv; -} - - -PR_STATIC_CALLBACK(int) -compareRecords(const void* e1, const void* e2, void* /*unused*/) -{ - const nsDiskCacheRecord* r1 = (const nsDiskCacheRecord*) e1; - const nsDiskCacheRecord* r2 = (const nsDiskCacheRecord*) e2; - return (r1->EvictionRank() - r2->EvictionRank()); -} - -nsresult nsDiskCacheDevice::evictDiskCacheEntries() -{ - nsresult rv; - - if (mCacheMap->DataSize() < mCacheCapacity) return NS_OK; - - // XXX make sure meta data is up to date. - rv = updateDiskCacheEntries(); - if (NS_FAILED(rv)) return rv; - - // 1. gather all records into an array, sorted by eviction rank. keep deleting them until we recover enough space. - PRUint32 count = 0; - nsDiskCacheRecord* sortedRecords = new nsDiskCacheRecord[mCacheMap->EntryCount()]; - if (sortedRecords) { - for (PRUint32 i = 0; i < nsDiskCacheMap::kBucketsPerTable; ++i) { - nsDiskCacheRecord* bucket = mCacheMap->GetBucket(i); - for (PRUint32 j = 0; j < nsDiskCacheMap::kRecordsPerBucket; ++j) { - nsDiskCacheRecord* record = bucket++; - if (record->HashNumber() == 0) - break; - NS_ASSERTION(count < mCacheMap->EntryCount(), "EntryCount is wrong"); - sortedRecords[count++] = *record; - } - } - NS_QuickSort((void*)sortedRecords, count, sizeof(nsDiskCacheRecord), compareRecords, nsnull); - } else { - return NS_ERROR_OUT_OF_MEMORY; - } - - // these are sorted by eviction rank. lower eviction ranks are more eligible for eviction. - for (PRUint32 i = 0; i < count; ++i) { - nsDiskCacheRecord* record = &sortedRecords[i]; - - // XXX if this entry is currently active, then leave it alone, - // as it is likely to be modified very soon. - nsDiskCacheEntry* diskEntry = mBoundEntries.GetEntry(record->HashNumber()); - if (diskEntry) continue; - - // delete the metadata file. - nsCOMPtr metaFile; - rv = getFileForHashNumber(record->HashNumber(), PR_TRUE, 0, getter_AddRefs(metaFile)); - if (NS_SUCCEEDED(rv)) { - rv = metaFile->Delete(PR_FALSE); - } - - PRUint32 dataSize = 0; - - // delete the data file - nsCOMPtr dataFile; - rv = getFileForHashNumber(record->HashNumber(), PR_FALSE, 0, getter_AddRefs(dataFile)); - if (NS_SUCCEEDED(rv)) { - PRInt64 fileSize; - rv = dataFile->GetFileSize(&fileSize); - if (NS_SUCCEEDED(rv)) - LL_L2I(dataSize, fileSize); - rv = dataFile->Delete(PR_FALSE); - } - - // remove from cache map. - nsDiskCacheRecord* deletedRecord = mCacheMap->GetRecord(record->HashNumber()); - if (record->HashNumber() == deletedRecord->HashNumber() && record->FileGeneration() == deletedRecord->FileGeneration()) - mCacheMap->DeleteRecord(deletedRecord); - - // update the cache size. - if ((mCacheMap->DataSize() -= dataSize) <= mCacheCapacity) - break; - } - - delete[] sortedRecords; - - return NS_OK; -} - -nsresult nsDiskCacheDevice::openCacheMap() -{ - nsresult rv; - nsCOMPtr file; - rv = mCacheDirectory->Clone(getter_AddRefs(file)); - if (NS_FAILED(rv)) return rv; - nsCOMPtr localFile(do_QueryInterface(file, &rv)); - if (NS_FAILED(rv)) return rv; - rv = localFile->Append("_CACHE_MAP_"); - if (NS_FAILED(rv)) return rv; - - mCacheStream = new nsANSIFileStream; - if (!mCacheStream) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(mCacheStream); - rv = mCacheStream->Open(localFile); - if (NS_FAILED(rv)) { - NS_RELEASE(mCacheStream); - return rv; - } - - return NS_OK; -} - -nsresult nsDiskCacheDevice::readCacheMap() -{ - nsresult rv; - if (!mCacheStream) { - rv = openCacheMap(); - if (NS_FAILED(rv)) return rv; - } - rv = mCacheMap->Read(mCacheStream); - return rv; -} - -nsresult nsDiskCacheDevice::writeCacheMap() -{ - nsresult rv; - if (!mCacheStream) { - rv = openCacheMap(); - if (NS_FAILED(rv)) return rv; - } - rv = mCacheMap->Write(mCacheStream); - return rv; -} - -nsresult nsDiskCacheDevice::updateCacheMap(nsDiskCacheEntry * diskEntry) -{ - nsresult rv; - - // get a record from the cache map, and use the fetch time for eviction ranking. - PRBool commitBucket = PR_TRUE; - nsDiskCacheRecord* record = mCacheMap->GetRecord(diskEntry->getHashNumber()); - if (record->HashNumber() != diskEntry->getHashNumber()) { - if (record->HashNumber() != 0) { - // eviction of eldest entry in this bucket. - evictDiskCacheRecord(record); - } else { - mCacheMap->EntryCount() += 1; - } - // newly bound record. fill in the blanks. - record->SetHashNumber(diskEntry->getHashNumber()); - record->SetEvictionRank(0); - record->SetFileGeneration(diskEntry->getGeneration()); - } else if (record->FileGeneration() != diskEntry->getGeneration()) { - // a collision has occurred - record->SetHashNumber(diskEntry->getHashNumber()); - record->SetEvictionRank(0); - record->SetFileGeneration(diskEntry->getGeneration()); - } else { - commitBucket = PR_FALSE; - record->SetEvictionRank(record->EvictionRank() + 1); - } - - // make sure the on-disk cache map is kept up to date. - if (commitBucket) { - if (!mCacheStream) { - rv = openCacheMap(); - if (NS_FAILED(rv)) return rv; - } - if (!mCacheMap->IsDirty()) { - mCacheMap->IsDirty() = PR_TRUE; - rv = mCacheMap->WriteHeader(mCacheStream); - } - rv = mCacheMap->WriteBucket(mCacheStream, mCacheMap->GetBucketIndex(record)); - } - - return rv; -} - -/** - * This evicts the disk cache entry that corresponds to the given record. - * If the entry happens to be bound, it must be doomed, otherwise, it can - * be eagerly removed from disk. - */ -nsresult nsDiskCacheDevice::evictDiskCacheRecord(nsDiskCacheRecord * record) -{ - nsresult rv; - - // if the entry is currently in use, then doom it rather than evicting right here. - nsDiskCacheEntry* diskEntry = mBoundEntries.GetEntry(record->HashNumber()); - if (diskEntry) - return nsCacheService::GlobalInstance()->DoomEntry_Locked(diskEntry->getCacheEntry()); - - // delete the metadata file. - nsCOMPtr metaFile; - rv = getFileForHashNumber(record->HashNumber(), PR_TRUE, - record->FileGeneration(), getter_AddRefs(metaFile)); - if (NS_SUCCEEDED(rv)) - rv = metaFile->Delete(PR_FALSE); - - // delete the data file - nsCOMPtr dataFile; - rv = getFileForHashNumber(record->HashNumber(), PR_FALSE, - record->FileGeneration(), getter_AddRefs(dataFile)); - if (NS_SUCCEEDED(rv)) - rv = dataFile->Delete(PR_FALSE); - - return rv; -} diff --git a/mozilla/netwerk/cache/src/nsDiskCacheDevice.h b/mozilla/netwerk/cache/src/nsDiskCacheDevice.h index bd58ad82d96..a04361d137d 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheDevice.h +++ b/mozilla/netwerk/cache/src/nsDiskCacheDevice.h @@ -26,19 +26,14 @@ #define _nsDiskCacheDevice_h_ #include "nsCacheDevice.h" +#include "nsDiskCacheBinding.h" +#include "nsDiskCacheBlockFile.h" #include "nsDiskCacheEntry.h" #include "nsILocalFile.h" #include "nsIObserver.h" -class nsDiskCacheEntry; class nsDiskCacheMap; -class nsDiskCacheRecord; - -class nsISupportsArray; -class nsIInputStream; -class nsIOutputStream; -class nsANSIFileStream; class nsDiskCacheDevice : public nsCacheDevice { public: @@ -70,52 +65,32 @@ public: virtual nsresult EvictEntries(const char * clientID); /* private: */ - void setPrefsObserver(nsIObserver* observer); + void setPrefsObserver(nsIObserver * observer); void getPrefsObserver(nsIObserver ** result); - void setCacheDirectory(nsILocalFile* directory); + void setCacheDirectory(nsILocalFile * directory); + void getCacheDirectory(nsILocalFile ** result); void setCacheCapacity(PRUint32 capacity); PRUint32 getCacheCapacity(); PRUint32 getCacheSize(); PRUint32 getEntryCount(); - - nsresult getFileForHashNumber(PLDHashNumber hashNumber, PRBool meta, PRUint32 generation, nsIFile ** result); - nsresult getFileForKey(const char* key, PRBool meta, PRUint32 generation, nsIFile ** result); - nsresult getFileForDiskCacheEntry(nsDiskCacheEntry * diskEntry, PRBool meta, nsIFile ** result); - - static nsresult getTransportForFile(nsIFile* file, nsCacheAccessMode mode, nsITransport ** result); - static nsresult openInputStream(nsIFile* file, nsIInputStream ** result); - static nsresult openOutputStream(nsIFile* file, nsIOutputStream ** result); - - nsresult visitEntries(nsICacheVisitor * visitory); - nsresult readDiskCacheEntry(const char * key, nsDiskCacheEntry ** diskEntry); - - nsresult updateDiskCacheEntries(); - nsresult updateDiskCacheEntry(nsDiskCacheEntry * diskEntry); - nsresult deleteDiskCacheEntry(nsDiskCacheEntry * diskEntry); +private: + /** + * Private methods + */ + nsresult InitializeCacheDirectory(); + nsresult GetCacheTrashDirectory(nsIFile ** result); + nsresult EvictDiskCacheEntries(); - nsresult scavengeDiskCacheEntries(nsDiskCacheEntry * diskEntry); - - nsresult scanDiskCacheEntries(nsISupportsArray ** result); - nsresult evictDiskCacheEntries(); - - nsresult clobberDiskCache(); - - nsresult openCacheMap(); - nsresult readCacheMap(); - nsresult writeCacheMap(); - - nsresult updateCacheMap(nsDiskCacheEntry * diskEntry); - nsresult evictDiskCacheRecord(nsDiskCacheRecord * record); - -private: - PRBool mInitialized; - nsCOMPtr mPrefsObserver; - nsCOMPtr mCacheDirectory; - nsDiskCacheEntryHashTable mBoundEntries; - PRUint32 mCacheCapacity; - nsDiskCacheMap* mCacheMap; - nsANSIFileStream* mCacheStream; + /** + * Member variables + */ + PRBool mInitialized; + nsCOMPtr mPrefsObserver; // XXX ? + nsCOMPtr mCacheDirectory; + nsDiskCacheBindery mBindery; + PRUint32 mCacheCapacity; // XXX need soft/hard limits, currentTotal + nsDiskCacheMap * mCacheMap; }; #endif // _nsDiskCacheDevice_h_ diff --git a/mozilla/netwerk/cache/src/nsDiskCacheEntry.cpp b/mozilla/netwerk/cache/src/nsDiskCacheEntry.cpp index f36297f682b..22a754888f5 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheEntry.cpp +++ b/mozilla/netwerk/cache/src/nsDiskCacheEntry.cpp @@ -10,7 +10,7 @@ * implied. See the License for the specific language governing * rights and limitations under the License. * - * The Original Code is nsMemoryCacheDevice.cpp, released February 22, 2001. + * The Original Code is nsDiskCacheEntry.cpp, released May 10, 2001. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are @@ -18,196 +18,194 @@ * Rights Reserved. * * Contributor(s): + * Gordon Sheridan * Patrick C. Beard */ -#include - +#include "nsDiskCache.h" #include "nsDiskCacheEntry.h" +#include "nsDiskCacheBinding.h" +#include "nsDiskCacheMap.h" -NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheEntry); +#include "nsCache.h" -PLDHashNumber -nsDiskCacheEntry::Hash(const char* key) -{ - PLDHashNumber h = 0; - for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s) - h = (h >> (PL_DHASH_BITS - 4)) ^ (h << 4) ^ *s; - return (h == 0 ? ULONG_MAX : h); -} /****************************************************************************** - * nsCacheEntryHashTable + * nsDiskCacheEntry *****************************************************************************/ -PLDHashTableOps nsDiskCacheEntryHashTable::ops = +/** + * CreateCacheEntry() + * + * Creates an nsCacheEntry and sets all fields except for the binding. + */ +nsCacheEntry * +nsDiskCacheEntry::CreateCacheEntry(nsCacheDevice * device) { - PL_DHashAllocTable, - PL_DHashFreeTable, - GetKey, - HashKey, - MatchEntry, - MoveEntry, - ClearEntry, - Finalize -}; - -nsDiskCacheEntryHashTable::nsDiskCacheEntryHashTable() - : initialized(PR_FALSE) -{ -} - - -nsDiskCacheEntryHashTable::~nsDiskCacheEntryHashTable() -{ - if (initialized) - PL_DHashTableFinish(&table); -} - - -nsresult -nsDiskCacheEntryHashTable::Init() -{ - nsresult rv = NS_OK; - initialized = PL_DHashTableInit(&table, &ops, nsnull, - sizeof(HashTableEntry), 512); - - if (!initialized) rv = NS_ERROR_OUT_OF_MEMORY; + nsCacheEntry * entry = nsnull; + nsresult rv = nsCacheEntry::Create(mKeyStart, + nsICache::STREAM_BASED, + nsICache::STORE_ON_DISK, + device, + &entry); + if (NS_FAILED(rv) || !entry) return nsnull; - return rv; -} - - -nsDiskCacheEntry * -nsDiskCacheEntryHashTable::GetEntry(const char * key) -{ - return GetEntry(nsDiskCacheEntry::Hash(key)); -} - - -nsDiskCacheEntry * -nsDiskCacheEntryHashTable::GetEntry(PLDHashNumber key) -{ - nsDiskCacheEntry * result = nsnull; - NS_ASSERTION(initialized, "nsDiskCacheEntryHashTable not initialized"); - HashTableEntry * hashEntry; - hashEntry = (HashTableEntry*) PL_DHashTableOperate(&table, (void*) key, PL_DHASH_LOOKUP); - if (PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { - result = hashEntry->mDiskCacheEntry; + entry->SetFetchCount(mFetchCount); + entry->SetLastFetched(mLastFetched); + entry->SetLastModified(mLastModified); + entry->SetExpirationTime(mExpirationTime); + entry->SetCacheDevice(device); + // XXX why does nsCacheService have to fill out device in BindEntry()? + entry->SetDataSize(mDataSize); + + rv = entry->UnflattenMetaData(&mKeyStart[mKeySize], mMetaDataSize); + if (NS_FAILED(rv)) { + delete entry; + return nsnull; } - return result; + + return entry; } -nsresult -nsDiskCacheEntryHashTable::AddEntry(nsDiskCacheEntry * entry) +/** + * CheckConsistency() + * + * Perform a few simple checks to verify the data looks reasonable. + */ +PRBool +nsDiskCacheEntry::CheckConsistency(PRUint32 size) { - NS_ENSURE_ARG_POINTER(entry); - NS_ASSERTION(initialized, "nsDiskCacheEntryHashTable not initialized"); - - HashTableEntry * hashEntry; - hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, - (void*) entry->getHashNumber(), - PL_DHASH_ADD); - if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY; + if ((mHeaderVersion != nsDiskCache::kCurrentVersion) || + (Size() > size) || + (mKeySize == 0) || + (mKeyStart[mKeySize - 1] != 0)) // key is null terminated + return PR_FALSE; - NS_ADDREF(hashEntry->mDiskCacheEntry = entry); + return PR_TRUE; +} + +/** + * CreateDiskCacheEntry(nsCacheEntry * entry) + * + * Prepare an nsCacheEntry for writing to disk + */ +nsDiskCacheEntry * +CreateDiskCacheEntry(nsDiskCacheBinding * binding) +{ + nsCacheEntry * entry = binding->mCacheEntry; + if (!entry) return nsnull; + + PRUint32 keySize = entry->Key()->Length() + 1; + PRUint32 size = sizeof(nsDiskCacheEntry) + + keySize + entry->MetaDataSize(); + + // pad size so we can write to block files without overrunning buffer + PRInt32 pad = size; + if (pad < 1024) pad = 1024; + else if (pad < 4096) pad = 4096; + else if (pad < 16384) pad = 16384; + // XXX be more precise + + nsDiskCacheEntry * diskEntry = (nsDiskCacheEntry *)new char[pad]; + if (!diskEntry) return nsnull; + + diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion; + diskEntry->mMetaLocation = binding->mRecord.MetaLocation(); + diskEntry->mFetchCount = entry->FetchCount(); + diskEntry->mLastFetched = entry->LastFetched(); + diskEntry->mLastModified = entry->LastModified(); + diskEntry->mExpirationTime = entry->ExpirationTime(); + diskEntry->mDataSize = entry->DataSize(); + diskEntry->mKeySize = keySize; + diskEntry->mMetaDataSize = entry->MetaDataSize(); + + nsCRT::memcpy(diskEntry->mKeyStart, entry->Key()->get(),keySize); + + // XXX FIXME FlattenMetaData should not allocate a buffer + char * metaData = nsnull; + PRUint32 metaSize = 0; + nsresult rv = entry->FlattenMetaData(&metaData, &metaSize); + if (NS_FAILED(rv)) { + delete diskEntry; + return nsnull; + } + + diskEntry->mMetaDataSize = metaSize; + if (metaSize) + nsCRT::memcpy(&diskEntry->mKeyStart[keySize], metaData, metaSize); + + delete metaData; + return diskEntry; +} + + +/****************************************************************************** + * nsDiskCacheEntryInfo + *****************************************************************************/ + +NS_IMPL_ISUPPORTS1(nsDiskCacheEntryInfo, nsICacheEntryInfo); + +NS_IMETHODIMP nsDiskCacheEntryInfo::GetClientID(char ** clientID) +{ + NS_ENSURE_ARG_POINTER(clientID); + return ClientIDFromCacheKey(nsLiteralCString(mDiskEntry->mKeyStart), clientID); +} + +extern const char DISK_CACHE_DEVICE_ID[]; +NS_IMETHODIMP nsDiskCacheEntryInfo::GetDeviceID(char ** deviceID) +{ + NS_ENSURE_ARG_POINTER(deviceID); + *deviceID = nsCRT::strdup(mDeviceID); + return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + + +NS_IMETHODIMP nsDiskCacheEntryInfo::GetKey(char ** clientKey) +{ + NS_ENSURE_ARG_POINTER(clientKey); + return ClientKeyFromCacheKey(nsLiteralCString(mDiskEntry->mKeyStart), clientKey); +} + +NS_IMETHODIMP nsDiskCacheEntryInfo::GetFetchCount(PRInt32 *aFetchCount) +{ + NS_ENSURE_ARG_POINTER(aFetchCount); + *aFetchCount = mDiskEntry->mFetchCount; return NS_OK; } - -void -nsDiskCacheEntryHashTable::RemoveEntry(nsDiskCacheEntry * entry) +NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched) { - NS_ASSERTION(initialized, "nsDiskCacheEntryHashTable not initialized"); - NS_ASSERTION(entry, "### cacheEntry == nsnull"); - - (void) PL_DHashTableOperate(&table, (void*) entry->getHashNumber(), PL_DHASH_REMOVE); + NS_ENSURE_ARG_POINTER(aLastFetched); + *aLastFetched = mDiskEntry->mLastFetched; + return NS_OK; } - -void -nsDiskCacheEntryHashTable::VisitEntries(Visitor *visitor) +NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastModified(PRUint32 *aLastModified) { - PL_DHashTableEnumerate(&table, VisitEntry, visitor); + NS_ENSURE_ARG_POINTER(aLastModified); + *aLastModified = mDiskEntry->mLastModified; + return NS_OK; } - -PLDHashOperator PR_CALLBACK -nsDiskCacheEntryHashTable::VisitEntry(PLDHashTable * table, - PLDHashEntryHdr * header, - PRUint32 number, - void * arg) +NS_IMETHODIMP nsDiskCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime) { - HashTableEntry* hashEntry = (HashTableEntry *) header; - Visitor *visitor = (Visitor*) arg; - return (visitor->VisitEntry(hashEntry->mDiskCacheEntry) ? PL_DHASH_NEXT : PL_DHASH_STOP); + NS_ENSURE_ARG_POINTER(aExpirationTime); + *aExpirationTime = mDiskEntry->mExpirationTime; + return NS_OK; } -/** - * hash table operation callback functions - */ -const void * PR_CALLBACK -nsDiskCacheEntryHashTable::GetKey(PLDHashTable * /*table*/, PLDHashEntryHdr * header) +NS_IMETHODIMP nsDiskCacheEntryInfo::IsStreamBased(PRBool *aStreamBased) { - HashTableEntry * hashEntry = (HashTableEntry *) header; - return (void*) hashEntry->mDiskCacheEntry->getHashNumber(); + NS_ENSURE_ARG_POINTER(aStreamBased); + *aStreamBased = PR_TRUE; + return NS_OK; } - -PLDHashNumber PR_CALLBACK -nsDiskCacheEntryHashTable::HashKey( PLDHashTable *table, const void *key) +NS_IMETHODIMP nsDiskCacheEntryInfo::GetDataSize(PRUint32 *aDataSize) { - return (PLDHashNumber) key; -} - -PRBool PR_CALLBACK -nsDiskCacheEntryHashTable::MatchEntry(PLDHashTable * /* table */, - const PLDHashEntryHdr * header, - const void * key) -{ - HashTableEntry * hashEntry = (HashTableEntry *) header; - return (hashEntry->mDiskCacheEntry->getHashNumber() == (PLDHashNumber) key); -} - -void PR_CALLBACK -nsDiskCacheEntryHashTable::MoveEntry(PLDHashTable * /* table */, - const PLDHashEntryHdr * fromHeader, - PLDHashEntryHdr * toHeader) -{ - HashTableEntry * fromEntry = (HashTableEntry *) fromHeader; - HashTableEntry * toEntry = (HashTableEntry *) toHeader; - toEntry->keyHash = fromEntry->keyHash; - toEntry->mDiskCacheEntry = fromEntry->mDiskCacheEntry; - fromEntry->mDiskCacheEntry = nsnull; -} - - -void PR_CALLBACK -nsDiskCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */, - PLDHashEntryHdr * header) -{ - HashTableEntry* hashEntry = (HashTableEntry *) header; - hashEntry->keyHash = 0; - NS_IF_RELEASE(hashEntry->mDiskCacheEntry); -} - - -void PR_CALLBACK -nsDiskCacheEntryHashTable::Finalize(PLDHashTable * table) -{ - (void) PL_DHashTableEnumerate(table, FreeCacheEntries, nsnull); -} - - -PLDHashOperator PR_CALLBACK -nsDiskCacheEntryHashTable::FreeCacheEntries(PLDHashTable * /* table */, - PLDHashEntryHdr * header, - PRUint32 number, - void * arg) -{ - HashTableEntry *entry = (HashTableEntry *) header; - NS_IF_RELEASE(entry->mDiskCacheEntry); - return PL_DHASH_NEXT; + NS_ENSURE_ARG_POINTER(aDataSize); + *aDataSize = mDiskEntry->mDataSize; + return NS_OK; } diff --git a/mozilla/netwerk/cache/src/nsDiskCacheEntry.h b/mozilla/netwerk/cache/src/nsDiskCacheEntry.h index df53281995a..162e1b431ca 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheEntry.h +++ b/mozilla/netwerk/cache/src/nsDiskCacheEntry.h @@ -17,148 +17,110 @@ * Copyright (C) 2001 Netscape Communications Corporation. All * Rights Reserved. * - * Contributor(s): + * Contributor(s): + * Gordon Sheridan * Patrick C. Beard */ #ifndef _nsDiskCacheEntry_h_ #define _nsDiskCacheEntry_h_ -#include "nspr.h" -#include "pldhash.h" +#include "nsDiskCacheMap.h" -#include "nsISupports.h" #include "nsCacheEntry.h" -#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS -#include "nsITransport.h" -#endif +#include "nsICacheVisitor.h" -class nsDiskCacheEntry : public nsISupports, public PRCList { +#include "nspr.h" +#include "nscore.h" +#include "nsError.h" + + + +/****************************************************************************** + * nsDiskCacheEntry + *****************************************************************************/ +struct nsDiskCacheEntry { + PRUint32 mHeaderVersion; // useful for stand-alone metadata files + PRUint32 mMetaLocation; // for verification + PRInt32 mFetchCount; + PRUint32 mLastFetched; + PRUint32 mLastModified; + PRUint32 mExpirationTime; + PRUint32 mDataSize; + PRUint32 mKeySize; // includes terminating null byte + PRUint32 mMetaDataSize; // includes terminating null byte + char mKeyStart[1]; // start of key data + // mMetaDataStart = mKeyStart[mKeySize]; + + PRUint32 Size() { return sizeof(nsDiskCacheEntry) + - sizeof(char) // subtract default key size + + mKeySize // plus actual key size + + mMetaDataSize; + } + + nsCacheEntry * CreateCacheEntry(nsCacheDevice * device); + + PRBool CheckConsistency(PRUint32 size); + + void Swap() // host to network (memory to disk) + { +#if defined(IS_LITTLE_ENDIAN) + mHeaderVersion = ::PR_htonl(mHeaderVersion); + mMetaLocation = ::PR_htonl(mMetaLocation); + mFetchCount = ::PR_htonl(mFetchCount); + mLastFetched = ::PR_htonl(mLastFetched); + mLastModified = ::PR_htonl(mLastModified); + mExpirationTime = ::PR_htonl(mExpirationTime); + mDataSize = ::PR_htonl(mDataSize); + mKeySize = ::PR_htonl(mKeySize); + mMetaDataSize = ::PR_htonl(mMetaDataSize); +#endif + } + + void Unswap() // network to host (disk to memory) + { +#if defined(IS_LITTLE_ENDIAN) + mHeaderVersion = ::PR_ntohl(mHeaderVersion); + mMetaLocation = ::PR_ntohl(mMetaLocation); + mFetchCount = ::PR_ntohl(mFetchCount); + mLastFetched = ::PR_ntohl(mLastFetched); + mLastModified = ::PR_ntohl(mLastModified); + mExpirationTime = ::PR_ntohl(mExpirationTime); + mDataSize = ::PR_ntohl(mDataSize); + mKeySize = ::PR_ntohl(mKeySize); + mMetaDataSize = ::PR_ntohl(mMetaDataSize); +#endif + } +}; + +nsDiskCacheEntry * CreateDiskCacheEntry(nsDiskCacheBinding * binding); + + + +/****************************************************************************** + * nsDiskCacheEntryInfo + *****************************************************************************/ +class nsDiskCacheEntryInfo : public nsICacheEntryInfo { public: NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYINFO - nsDiskCacheEntry(nsCacheEntry* entry) - : mCacheEntry(entry), - mGeneration(0) + nsDiskCacheEntryInfo(const char * deviceID, nsDiskCacheEntry * diskEntry) + : mDeviceID(deviceID) + , mDiskEntry(diskEntry) { NS_INIT_ISUPPORTS(); - PR_INIT_CLIST(this); - mHashNumber = Hash(entry->Key()->get()); } - virtual ~nsDiskCacheEntry() - { - PR_REMOVE_LINK(this); - } - -#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS - /** - * Maps a cache access mode to a cached nsITransport for that access - * mode. We keep these cached to avoid repeated trips to the - * file transport service. - */ - nsCOMPtr& getTransport(nsCacheAccessMode mode) - { - return mTransports[mode - 1]; - } -#endif + virtual ~nsDiskCacheEntryInfo() {} - nsCacheEntry* getCacheEntry() - { - return mCacheEntry; - } - - PRUint32 getGeneration() - { - return mGeneration; - } - - void setGeneration(PRUint32 generation) - { - mGeneration = generation; - } - - PLDHashNumber getHashNumber() - { - return mHashNumber; - } - - nsrefcnt getRefCount() - { - return mRefCnt; - } - - static PLDHashNumber Hash(const char* key); + const char* Key() { return mDiskEntry->mKeyStart; } private: -#ifdef MOZ_NEW_CACHE_REUSE_TRANSPORTS - nsCOMPtr mTransports[3]; -#endif - nsCacheEntry* mCacheEntry; - PRUint32 mGeneration; - PLDHashNumber mHashNumber; + const char * mDeviceID; + nsDiskCacheEntry * mDiskEntry; }; -class nsDiskCacheEntryHashTable { -public: - nsDiskCacheEntryHashTable(); - ~nsDiskCacheEntryHashTable(); - - nsresult Init(); - - nsDiskCacheEntry * GetEntry(const char * key); - nsDiskCacheEntry * GetEntry(PLDHashNumber key); - nsresult AddEntry(nsDiskCacheEntry * entry); - void RemoveEntry(nsDiskCacheEntry * entry); - - class Visitor { - public: - virtual PRBool VisitEntry(nsDiskCacheEntry * entry) = 0; - }; - - void VisitEntries(Visitor * visitor); - -private: - struct HashTableEntry : PLDHashEntryHdr { - nsDiskCacheEntry * mDiskCacheEntry; // STRONG ref? - }; - - // PLDHashTable operation callbacks - static const void * PR_CALLBACK GetKey(PLDHashTable * table, - PLDHashEntryHdr * entry); - - static PLDHashNumber PR_CALLBACK HashKey(PLDHashTable * table, - const void * key); - - static PRBool PR_CALLBACK MatchEntry(PLDHashTable * table, - const PLDHashEntryHdr * entry, - const void * key); - - static void PR_CALLBACK MoveEntry(PLDHashTable * table, - const PLDHashEntryHdr * from, - PLDHashEntryHdr * to); - - static void PR_CALLBACK ClearEntry(PLDHashTable * table, - PLDHashEntryHdr * entry); - - static void PR_CALLBACK Finalize(PLDHashTable *table); - - static - PLDHashOperator PR_CALLBACK FreeCacheEntries(PLDHashTable * table, - PLDHashEntryHdr * hdr, - PRUint32 number, - void * arg); - static - PLDHashOperator PR_CALLBACK VisitEntry(PLDHashTable * table, - PLDHashEntryHdr * hdr, - PRUint32 number, - void * arg); - - // member variables - static PLDHashTableOps ops; - PLDHashTable table; - PRBool initialized; -}; #endif /* _nsDiskCacheEntry_h_ */ diff --git a/mozilla/netwerk/cache/src/nsDiskCacheMap.cpp b/mozilla/netwerk/cache/src/nsDiskCacheMap.cpp index 07c2578e952..180d2dcc77d 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheMap.cpp +++ b/mozilla/netwerk/cache/src/nsDiskCacheMap.cpp @@ -19,245 +19,814 @@ * * Contributor(s): * Patrick C. Beard + * Gordon Sheridan */ #include "nsDiskCacheMap.h" -#include "nsIFileStreams.h" +#include "nsDiskCacheBinding.h" +#include "nsDiskCacheEntry.h" + +#include "nsCRT.h" #include -nsDiskCacheMap::nsDiskCacheMap() + +/****************************************************************************** + * nsDiskCacheBucket + *****************************************************************************/ + +void +nsDiskCacheBucket::Swap() { -} - -nsDiskCacheMap::~nsDiskCacheMap() -{ -} - -void nsDiskCacheMap::Reset() -{ - mHeader.mDataSize = 0; - mHeader.mEntryCount = 0; - mHeader.mIsDirty = PR_TRUE; - - for (PRUint32 b = 0; b < kBucketsPerTable; ++b) { - nsDiskCacheBucket& bucket = mBuckets[b]; - ::memset(&bucket, 0, sizeof(nsDiskCacheBucket)); - } -} - -nsDiskCacheRecord* nsDiskCacheMap::GetRecord(PRUint32 hashNumber) -{ - nsDiskCacheBucket& bucket = mBuckets[GetBucketIndex(hashNumber)]; - nsDiskCacheRecord* oldestRecord = &bucket.mRecords[0]; - - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0 || record->HashNumber() == hashNumber) - return record; - if (record->EvictionRank() < oldestRecord->EvictionRank()) - oldestRecord = record; - } - // if we don't find an empty record, return the oldest record for eviction. - return oldestRecord; -} - - -void nsDiskCacheMap::DeleteRecord(nsDiskCacheRecord* deletedRecord) -{ - PRUint32 hashNumber = deletedRecord->HashNumber(); - nsDiskCacheBucket& bucket = mBuckets[GetBucketIndex(hashNumber)]; - NS_ASSERTION(deletedRecord >= &bucket.mRecords[0] && - deletedRecord < &bucket.mRecords[kRecordsPerBucket], - "invalid record to delete."); - nsDiskCacheRecord* limit = &bucket.mRecords[kRecordsPerBucket]; - nsDiskCacheRecord* lastRecord = nsnull; - // XXX use binary search to find the end, much quicker. - // find the last record, to fill in the deleted record. - for (nsDiskCacheRecord* record = deletedRecord + 1; record < limit; ++record) { - if (record->HashNumber() == 0) { - lastRecord = record - 1; + nsDiskCacheRecord * record = &mRecords[0]; + for (int i = 0; i < kRecordsPerBucket; ++i) { + if (record->HashNumber() == 0) break; - } + record->Swap(); } - // copy the last record, to the newly deleted record. - if (lastRecord && deletedRecord != lastRecord) { - *deletedRecord = *lastRecord; - deletedRecord = lastRecord; +} + +void +nsDiskCacheBucket::Unswap() +{ + nsDiskCacheRecord * record = &mRecords[0]; + for (int i = 0; i < kRecordsPerBucket; ++i) { + if (record->HashNumber() == 0) + break; + record->Unswap(); } - // mark record as free. - deletedRecord->SetHashNumber(0); - // reduce the number of entries. - mHeader.mEntryCount--; } -nsresult nsDiskCacheMap::Read(nsIInputStream* input) +PRUint32 +nsDiskCacheBucket::CountRecords() { - nsresult rv; - PRUint32 count; - - // seek to beginning of the file. - nsCOMPtr seekable = do_QueryInterface(input, &rv); - if (NS_FAILED(rv)) return rv; - rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); - if (NS_FAILED(rv)) return rv; - - // read the header. - nsDiskCacheHeader header; - rv = input->Read((char*)&header, sizeof(header), &count); - if (count != sizeof(header)) return NS_ERROR_FAILURE; - if (NS_FAILED(rv)) return rv; - header.Unswap(); + if (mRecords[0].HashNumber() == 0) return 0; - // validate the version. - if (header.mVersion != nsDiskCacheHeader::kCurrentVersion) return NS_ERROR_FAILURE; - mHeader = header; - - // seek to beginning of first bucket. - rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, sizeof(nsDiskCacheBucket)); - if (NS_FAILED(rv)) return rv; - - // read the buckets. - rv = input->Read((char*)&mBuckets, sizeof(mBuckets), &count); - if (count != sizeof(mBuckets)) return NS_ERROR_FAILURE; - if (NS_FAILED(rv)) return rv; + PRUint32 i = kRecordsPerBucket >> 1; + PRUint32 offset = kRecordsPerBucket >> 2; - // unswap all of the active records. - for (int b = 0; b < kBucketsPerTable; ++b) { - nsDiskCacheBucket& bucket = mBuckets[b]; - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0) - break; - record->Unswap(); + 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 rank = 0; + for (int i = CountRecords() - 1; i >= 0; --i) { + if (rank < mRecords[i].EvictionRank()) + 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 (equal or greater than evictionRank) + 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 file; + rv = cacheDirectory->Clone(getter_AddRefs(file)); + nsCOMPtr 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::Write(nsIOutputStream* output) + +nsresult +nsDiskCacheMap::FlushBuckets(PRBool unswap) { - nsresult rv; - PRUint32 count; - - // seek to beginning of the file. - nsCOMPtr seekable = do_QueryInterface(output, &rv); - if (NS_FAILED(rv)) return rv; - rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); - if (NS_FAILED(rv)) return rv; + if (!mMapFD) return NS_ERROR_NOT_AVAILABLE; - // write the header. - nsDiskCacheHeader header = mHeader; - header.Swap(); - rv = output->Write((char*)&header, sizeof(header), &count); - if (count != sizeof(header)) return NS_ERROR_FAILURE; - if (NS_FAILED(rv)) return rv; + // seek to beginning of buckets + PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET); + if (filePos != sizeof(nsDiskCacheHeader)) return NS_ERROR_UNEXPECTED; - // pad the rest of the header to sizeof(nsDiskCacheBucket). - char padding[sizeof(nsDiskCacheBucket) - sizeof(nsDiskCacheHeader)]; - ::memset(padding, 0, sizeof(padding)); - rv = output->Write(padding, sizeof(padding), &count); - if (count != sizeof(padding)) return NS_ERROR_FAILURE; - if (NS_FAILED(rv)) return rv; + // Swap each bucket + for (PRUint32 i = 0; i < kBucketsPerTable; ++i) { + mBuckets[i].Swap(); + } + + PRInt32 bytesWritten = PR_Write(mMapFD, &mBuckets, sizeof(nsDiskCacheBucket) * kBucketsPerTable); - // swap all of the active records. - { - for (int b = 0; b < kBucketsPerTable; ++b) { - nsDiskCacheBucket& bucket = mBuckets[b]; - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0) - break; - record->Swap(); + 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(), "whao!"); + 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(); + } + +NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(), "whao!"); + 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(); + +NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(), "whao!"); + 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(); + } + +NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(), "whao!"); + 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(); + mHeader.mEntryCount -= recordsDeleted; + // XXX write bucket + } +NS_ASSERTION(mHeader.mEvictionRank[i] == mBuckets[i].EvictionRank(), "whao!"); + 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) +{ + while (1) { + + // find bucket with highest eviction rank + PRUint32 rank = 0; + PRUint32 index = 0; + for (int i = 0; i < kBucketsPerTable; ++i) { + if (rank < mHeader.mEvictionRank[i]) { + rank = mHeader.mEvictionRank[i]; + index = i; + } + } + +NS_ASSERTION(mHeader.mEvictionRank[index] == mBuckets[index].EvictionRank(), + "header eviction rank out of sync"); + + // visit records in bucket with eviction ranks >= target eviction rank + PRUint32 recordsDeleted; + PRBool continueFlag = mBuckets[index].VisitEachRecord(visitor, rank, &recordsDeleted); + if (recordsDeleted) { + // recalc eviction rank + mHeader.mEvictionRank[index] = mBuckets[index].EvictionRank(); + mHeader.mEntryCount -= recordsDeleted; + // XXX write bucket + } + if (!continueFlag) break; + + // break if visitor returned stop + + } + return NS_OK; +} + + + +nsresult +nsDiskCacheMap::OpenBlockFiles() +{ + // create nsILocalFile for block file + nsCOMPtr 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; + nsDiskCacheEntry * diskEntry = nsnull; + PRUint32 metaFile = record->MetaFile(); + PRFileDesc * fd = nsnull; + *result = nsnull; + + if (metaFile == 0) { // entry/metadata stored in separate file + // open and read the file + nsCOMPtr file; + rv = GetLocalFileForDiskCacheRecord(record, nsDiskCache::kMetaData, getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + + PRFileDesc * fd = nsnull; + nsresult 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 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; + + 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)) return rv; } } - // write the buckets. - rv = output->Write((char*)&mBuckets, sizeof(mBuckets), &count); - output->Flush(); + if (fileIndex == 0) { + // Write entry data to separate file + PRUint32 metaFileSizeK = ((size + 0x0399) >> 10); // round up to nearest 1k + nsCOMPtr 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; - // unswap all of the active records. - { - for (int b = 0; b < kBucketsPerTable; ++b) { - nsDiskCacheBucket& bucket = mBuckets[b]; - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0) - break; - record->Unswap(); - } + 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 != 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)); } - if (count != sizeof(mBuckets)) return NS_ERROR_FAILURE; +exit: + return rv; } -nsresult nsDiskCacheMap::WriteBucket(nsIOutputStream* output, PRUint32 index) + +nsresult +nsDiskCacheMap::DoomRecord(nsDiskCacheRecord * record) { - nsresult rv; - - // can only do this if the stream is seekable. - nsCOMPtr seekable = do_QueryInterface(output, &rv); - if (NS_FAILED(rv)) return rv; + nsresult rv = DeleteRecord(record); + // XXX future: add record to doomed record journal - // seek to the offset of this bucket, (index + 1) to skip the header. - rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, (index + 1) * sizeof(nsDiskCacheBucket)); - if (NS_FAILED(rv)) return rv; - - nsDiskCacheBucket& bucket = mBuckets[index]; - - // swap all of the active records. - { - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0) - break; - record->Swap(); - } - } - - PRUint32 count; - rv = output->Write((char*)&bucket, sizeof(nsDiskCacheBucket), &count); - output->Flush(); - - // unswap all of the active records. - { - for (int r = 0; r < kRecordsPerBucket; ++r) { - nsDiskCacheRecord* record = &bucket.mRecords[r]; - if (record->HashNumber() == 0) - break; - record->Unswap(); - } - } - - NS_ASSERTION(count == sizeof(nsDiskCacheBucket), "nsDiskCacheMap::WriteBucket failed"); - if (count != sizeof(nsDiskCacheBucket)) return NS_ERROR_FAILURE; - return rv; } -nsresult nsDiskCacheMap::WriteHeader(nsIOutputStream* output) + +nsresult +nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record) { - nsresult rv; - PRUint32 count; + 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; + PRUint32 fileIndex = metaData ? record->MetaFile() : record->DataFile(); + nsCOMPtr file; - // can only do this if the stream is seekable. - nsCOMPtr seekable = do_QueryInterface(output, &rv); - if (NS_FAILED(rv)) return rv; + if (fileIndex == 0) { + // delete the file + PRUint32 sizeK = metaData ? record->MetaFileSize() : record->DataFileSize(); + // XXX if sizeK == USHRT_MAX, stat file for actual size - rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); - if (NS_FAILED(rv)) return rv; - - // write the header. - nsDiskCacheHeader header = mHeader; - header.Swap(); - rv = output->Write((char*)&header, sizeof(header), &count); - output->Flush(); - if (count != sizeof(header)) return NS_ERROR_FAILURE; + rv = GetFileForDiskCacheRecord(record, metaData, getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + rv = file->Delete(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 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 file; + nsresult rv = GetFileForDiskCacheRecord(record, meta, getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr 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 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 localFile = do_QueryInterface(file, &rv); + NS_IF_ADDREF(*result = localFile); + + return rv; +} + + +PRUint32 +nsDiskCacheMap::GetBlockSizeForIndex(PRUint32 index) +{ + return 256 << (2 * (index)); // XXX magic numbers + +} diff --git a/mozilla/netwerk/cache/src/nsDiskCacheMap.h b/mozilla/netwerk/cache/src/nsDiskCacheMap.h index 9e41ee012df..5dbdd3f833a 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheMap.h +++ b/mozilla/netwerk/cache/src/nsDiskCacheMap.h @@ -19,97 +19,479 @@ * * Contributor(s): * Patrick C. Beard + * Gordon Sheridan */ #ifndef _nsDiskCacheMap_h_ +#define _nsDiskCacheMap_h_ + +#include + +#include "prtypes.h" +#include "prnetdb.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsILocalFile.h" #include "nsDiskCache.h" -#include "nsError.h" +#include "nsDiskCacheBlockFile.h" + + +class nsDiskCacheBinding; +class nsDiskCacheEntry; -class nsIInputStream; -class nsIOutputStream; +/****************************************************************************** + * nsDiskCacheRecord + * + * Cache Location Format + * + * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit + * + * 0011 0000 0000 0000 0000 0000 0000 0000 : File Selector (0 = separate file) + * 0000 0011 0000 0000 0000 0000 0000 0000 : number of extra contiguous blocks 1-4 + * 0100 1100 0000 0000 0000 0000 0000 0000 : reserved bits + * 0000 0000 1111 1111 1111 1111 1111 1111 : block# 0-16777216 (2^24) + * + * 0000 0000 1111 1111 1111 1111 0000 0000 : eFileSizeMask (size of file in k: see note) + * 0000 0000 0000 0000 0000 0000 1111 1111 : eFileGenerationMask + * + * File Selector: + * 0 = separate file on disk + * 1 = 256 byte block file + * 2 = 1k block file + * 3 = 4k block file + * + * eFileSizeMask note: Files larger than 64 Mb have zero size stored in the + * location. The file itself must be examined to determine + * its actual size. + * + *****************************************************************************/ -struct nsDiskCacheHeader { - enum { kCurrentVersion = 0x00010002 }; +class nsDiskCacheRecord { - PRUint32 mVersion; // cache version. - PRUint32 mDataSize; // size of cache in bytes. - PRUint32 mEntryCount; // number of entries stored in cache. - PRUint32 mIsDirty; // dirty flag. - // XXX need a bitmap? - - nsDiskCacheHeader() - : mVersion(kCurrentVersion), mDataSize(0), - mEntryCount(0), mIsDirty(PR_TRUE) +private: + PRUint32 mHashNumber; + PRUint32 mEvictionRank; + PRUint32 mDataLocation; + PRUint32 mMetaLocation; + + enum { + eLocationInitializedMask = 0x80000000, + + eLocationSelectorMask = 0x30000000, + eLocationSelectorOffset = 28, + + eExtraBlocksMask = 0x03000000, + eExtraBlocksOffset = 24, + + eReservedMask = 0x4C000000, + + eBlockNumberMask = 0x00FFFFFF, + + eFileSizeMask = 0x00FFFF00, + eFileSizeOffset = 8, + eFileGenerationMask = 0x000000FF, + eFileReservedMask = 0x4F000000 + + }; + +public: + nsDiskCacheRecord() + : mHashNumber(0), mEvictionRank(0), mDataLocation(0), mMetaLocation(0) { } + + PRBool ValidRecord() + { + if ((mDataLocation & eReservedMask) || (mMetaLocation & eReservedMask)) + return PR_FALSE; + return PR_TRUE; + } + + // HashNumber accessors + PRUint32 HashNumber() const { return mHashNumber; } + void SetHashNumber( PRUint32 hashNumber) { mHashNumber = hashNumber; } + + // EvictionRank accessors + PRUint32 EvictionRank() const { return mEvictionRank; } + void SetEvictionRank( PRUint32 rank) { mEvictionRank = rank; } + + // DataLocation accessors + PRBool DataLocationInitialized() { return mDataLocation & eLocationInitializedMask; } + + PRUint32 DataLocation() { return mDataLocation; } + void SetDataLocation( PRUint32 location) { mDataLocation = location; } + + PRUint32 DataFile() const + { + return (PRUint32)(mDataLocation & eLocationSelectorMask) >> eLocationSelectorOffset; + } + + void SetDataBlocks( PRUint32 index, PRUint32 startBlock, PRUint32 blockCount) + { + // clear everything + mDataLocation = 0; + + // set file index + NS_ASSERTION( index < 4,"invalid location index"); + NS_ASSERTION( index > 0,"invalid location index"); + mDataLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask; + + // set startBlock + NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number"); + mDataLocation |= startBlock & eBlockNumberMask; + + // set blockCount + NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count"); + blockCount = --blockCount; + mDataLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask; + + mDataLocation |= eLocationInitializedMask; + } + + PRUint32 DataBlockCount() const + { + return (PRUint32)((mDataLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1; + } + + PRUint32 DataStartBlock() const + { + return (mDataLocation & eBlockNumberMask); + } + + PRUint32 DataFileSize() const { return (mDataLocation & eFileSizeMask) >> eFileSizeOffset; } + void SetDataFileSize(PRUint32 size) + { + NS_ASSERTION((mDataLocation & eFileReservedMask) == 0, "bad location"); + mDataLocation &= ~eFileSizeMask; // clear eFileSizeMask + mDataLocation |= (size << eFileSizeOffset) & eFileSizeMask; + } + + PRUint16 DataFileGeneration() const + { + return (mDataLocation & eFileGenerationMask); + } + + void SetDataFileGeneration( PRUint8 generation) + { + // clear everything, (separate file index = 0) + mDataLocation = 0; + mDataLocation |= generation & eFileGenerationMask; + mDataLocation |= eLocationInitializedMask; + } + + // MetaLocation accessors + PRBool MetaLocationInitialized() { return mMetaLocation & eLocationInitializedMask; } + + PRUint32 MetaLocation() { return mMetaLocation; } + void SetMetaLocation( PRUint32 location) { mMetaLocation = location; } + + PRUint32 MetaFile() const + { + return (PRUint32)(mMetaLocation & eLocationSelectorMask) >> eLocationSelectorOffset; + } + + void SetMetaBlocks( PRUint32 index, PRUint32 startBlock, PRUint32 blockCount) + { + // clear everything + mMetaLocation = 0; + + // set file index + NS_ASSERTION( index < 4, "invalid location index"); + NS_ASSERTION( index > 0, "invalid location index"); + mMetaLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask; + + // set startBlock + NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number"); + mMetaLocation |= startBlock & eBlockNumberMask; + + // set blockCount + NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count"); + blockCount = --blockCount; + mMetaLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask; + + mMetaLocation |= eLocationInitializedMask; + } + + PRUint32 MetaBlockCount() const + { + return (PRUint32)((mMetaLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1; + } + + PRUint32 MetaStartBlock() const + { + return (mMetaLocation & eBlockNumberMask); + } + + PRUint32 MetaFileSize() const { return (mMetaLocation & eFileSizeMask) >> eFileSizeOffset; } + void SetMetaFileSize(PRUint32 size) + { + mMetaLocation &= ~eFileSizeMask; // clear eFileSizeMask + mMetaLocation |= (size << eFileSizeOffset) & eFileSizeMask; + } + + PRUint16 MetaFileGeneration() const + { + return (mMetaLocation & eFileGenerationMask); + } + + void SetMetaFileGeneration( PRUint8 generation) + { + // clear everything, (separate file index = 0) + mMetaLocation = 0; + mMetaLocation |= generation & eFileGenerationMask; + mMetaLocation |= eLocationInitializedMask; + } + + PRUint8 Generation() const + { + if ((mDataLocation & eLocationInitializedMask) && + (DataFile() == 0)) + return DataFileGeneration(); + + if ((mMetaLocation & eLocationInitializedMask) && + (MetaFile() == 0)) + return MetaFileGeneration(); + + return 0; // no generation + } + + void Swap() { - mVersion = ::PR_htonl(mVersion); - mDataSize = ::PR_htonl(mDataSize); - mEntryCount = ::PR_htonl(mEntryCount); - mIsDirty = ::PR_htonl(mIsDirty); +#if defined(IS_LITTLE_ENDIAN) + mHashNumber = ::PR_htonl(mHashNumber); + mEvictionRank = ::PR_htonl(mEvictionRank); + mDataLocation = ::PR_htonl(mDataLocation); + mMetaLocation = ::PR_htonl(mMetaLocation); +#endif } void Unswap() { - mVersion = ::PR_ntohl(mVersion); - mDataSize = ::PR_ntohl(mDataSize); - mEntryCount = ::PR_ntohl(mEntryCount); - mIsDirty = ::PR_ntohl(mIsDirty); +#if defined(IS_LITTLE_ENDIAN) + mHashNumber = ::PR_ntohl(mHashNumber); + mEvictionRank = ::PR_ntohl(mEvictionRank); + mDataLocation = ::PR_ntohl(mDataLocation); + mMetaLocation = ::PR_ntohl(mMetaLocation); +#endif + } + +}; + + +/****************************************************************************** + * nsDiskCacheRecordVisitor + *****************************************************************************/ + +enum { kDeleteRecordAndContinue = -1, + kStopVisitingRecords = 0, + kVisitNextRecord = 1 +}; + +class nsDiskCacheRecordVisitor { + public: + + virtual PRInt32 VisitRecord( nsDiskCacheRecord * mapRecord) = 0; +}; + + +/****************************************************************************** + * nsDiskCacheBucket + *****************************************************************************/ +enum { + kRecordsPerBucket = 256, + kBucketsPerTable = (1 << 5) // must be a power of 2! +}; + +struct nsDiskCacheBucket { + nsDiskCacheRecord mRecords[kRecordsPerBucket]; + + void Swap(); + void Unswap(); + PRUint32 CountRecords(); + PRUint32 EvictionRank(); // return largest rank in bucket + PRInt32 VisitEachRecord( nsDiskCacheRecordVisitor * visitor, + PRUint32 evictionRank, + PRUint32 * recordsDeleted); +}; + + +/****************************************************************************** + * nsDiskCacheHeader + *****************************************************************************/ + +struct nsDiskCacheHeader { + PRUint32 mVersion; // cache version. + PRInt32 mDataSize; // size of cache in bytes. + PRInt32 mEntryCount; // number of entries stored in cache. + PRUint32 mIsDirty; // dirty flag. + PRUint32 mEvictionRank[kBucketsPerTable]; + + // pad to blocksize + enum { kReservedBytes = sizeof(nsDiskCacheBucket) + - sizeof(PRUint32) * 4 // version, size, count, dirty + - sizeof(PRUint32) * kBucketsPerTable // eviction array + }; + + PRUint8 reserved[kReservedBytes]; + + // XXX need a bitmap? + + nsDiskCacheHeader() + : mVersion(nsDiskCache::kCurrentVersion) + , mDataSize(0) + , mEntryCount(0) + , mIsDirty(PR_TRUE) + {} + + void Swap() + { +#if defined(IS_LITTLE_ENDIAN) + mVersion = ::PR_htonl(mVersion); + mDataSize = ::PR_htonl(mDataSize); + mEntryCount = ::PR_htonl(mEntryCount); + mIsDirty = ::PR_htonl(mIsDirty); +#endif + } + + void Unswap() + { +#if defined(IS_LITTLE_ENDIAN) + mVersion = ::PR_ntohl(mVersion); + mDataSize = ::PR_ntohl(mDataSize); + mEntryCount = ::PR_ntohl(mEntryCount); + mIsDirty = ::PR_ntohl(mIsDirty); +#endif } }; -// XXX initial capacity, enough for 8192 distinct entries. + +/****************************************************************************** + * nsDiskCacheMap + *****************************************************************************/ + +// XXX fixed capacity for 8192 entries. Future: make dynamic + +enum { + kCacheMapSize = sizeof(nsDiskCacheHeader) + + kBucketsPerTable * sizeof(nsDiskCacheBucket) +}; + + class nsDiskCacheMap { public: - nsDiskCacheMap(); - ~nsDiskCacheMap(); - void Reset(); + nsDiskCacheMap() + : mCacheDirectory(nsnull) + , mMapFD(nsnull) + { + NS_ASSERTION(sizeof(nsDiskCacheHeader) == sizeof(nsDiskCacheBucket), "structure misalignment"); + } + ~nsDiskCacheMap() { (void) Close(); } + +/** + * File Operations + * + * Open + * + * Creates a new cache map file if one doesn't exist. + * Returns error if it detects change in format or cache wasn't closed. + */ + nsresult Open( nsILocalFile * cacheDirectory); + nsresult Close(); + +// nsresult Flush(); + nsresult FlushHeader(); + nsresult FlushBuckets( PRBool unswap); + +/** + * Record operations + */ + nsresult AddRecord( nsDiskCacheRecord * mapRecord, nsDiskCacheRecord * oldRecord); + nsresult UpdateRecord( nsDiskCacheRecord * mapRecord); + nsresult FindRecord( PRUint32 hashNumber, nsDiskCacheRecord * mapRecord); + nsresult DeleteRecord( nsDiskCacheRecord * mapRecord); + nsresult VisitRecords( nsDiskCacheRecordVisitor * visitor); + nsresult EvictRecords( nsDiskCacheRecordVisitor * visitor); + +/** + * Disk Entry operations + */ + nsresult DoomRecord( nsDiskCacheRecord * record); + nsresult DeleteStorage( nsDiskCacheRecord * record); + nsresult DeleteRecordAndStorage( nsDiskCacheRecord * record); + + nsresult GetFileForDiskCacheRecord( nsDiskCacheRecord * record, + PRBool meta, + nsIFile ** result); + + nsresult GetLocalFileForDiskCacheRecord( nsDiskCacheRecord * record, + PRBool meta, + nsILocalFile ** result); + + nsresult ReadDiskCacheEntry( nsDiskCacheRecord * record, + nsDiskCacheEntry ** result); + + nsresult WriteDiskCacheEntry( nsDiskCacheBinding * binding); - PRUint32& DataSize() { return mHeader.mDataSize; } - PRUint32& EntryCount() { return mHeader.mEntryCount; } - PRUint32& IsDirty() { return mHeader.mIsDirty; } + /** + * Statistical Operations + */ + void IncrementTotalSize( PRInt32 delta) + { + NS_ASSERTION(mHeader.mDataSize >= 0, "disk cache size negative?"); + mHeader.mDataSize += delta; + mHeader.mIsDirty = PR_TRUE; + } + + void DecrementTotalSize( PRInt32 delta) + { + mHeader.mDataSize -= delta; + mHeader.mIsDirty = PR_TRUE; + NS_ASSERTION(mHeader.mDataSize >= 0, "disk cache size negative?"); + } - nsDiskCacheRecord* GetRecord(PRUint32 hashNumber); - void DeleteRecord(nsDiskCacheRecord* record); + PRInt32 TotalSize() { return mHeader.mDataSize; } - enum { - kRecordsPerBucket = 256, - kBucketsPerTable = (1 << 5) // must be a power of 2! - }; + PRInt32 EntryCount() { return mHeader.mEntryCount; } + + +private: + + /** + * Private methods + */ + nsresult OpenBlockFiles(); + nsresult CloseBlockFiles(); + + nsresult GetBlockFileForIndex( PRUint32 index, nsILocalFile ** result); + PRUint32 GetBlockSizeForIndex( PRUint32 index); - nsDiskCacheRecord* GetBucket(PRUint32 index) + nsresult DeleteStorage( nsDiskCacheRecord * record, PRBool metaData); + + nsresult GetBucketForHashNumber( PRUint32 hashNumber, nsDiskCacheBucket ** result) { - return mBuckets[index].mRecords; + *result = &mBuckets[GetBucketIndex(hashNumber)]; + return NS_OK; } - - PRUint32 GetBucketIndex(PRUint32 hashNumber) + + PRUint32 GetBucketIndex( PRUint32 hashNumber) { return (hashNumber & (kBucketsPerTable - 1)); } - PRUint32 GetBucketIndex(nsDiskCacheRecord* record) - { - return GetBucketIndex(record->HashNumber()); - } - - nsresult Read(nsIInputStream* input); - nsresult Write(nsIOutputStream* output); - - nsresult ReadBucket(nsIInputStream* input, PRUint32 index); - nsresult WriteBucket(nsIOutputStream* output, PRUint32 index); - - nsresult ReadHeader(nsIInputStream* input); - nsresult WriteHeader(nsIOutputStream* output); - + + + +/** + * data members + */ private: - struct nsDiskCacheBucket { - nsDiskCacheRecord mRecords[kRecordsPerBucket]; - }; - + nsCOMPtr mCacheDirectory; + PRFileDesc * mMapFD; + nsDiskCacheBlockFile mBlockFile[3]; nsDiskCacheHeader mHeader; nsDiskCacheBucket mBuckets[kBucketsPerTable]; };