michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 cin et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef _nsDiskCacheMap_h_ michael@0: #define _nsDiskCacheMap_h_ michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include michael@0: michael@0: #include "prnetdb.h" michael@0: #include "nsDebug.h" michael@0: #include "nsError.h" michael@0: #include "nsIFile.h" michael@0: #include "nsITimer.h" michael@0: michael@0: #include "nsDiskCache.h" michael@0: #include "nsDiskCacheBlockFile.h" michael@0: michael@0: michael@0: class nsDiskCacheBinding; michael@0: struct nsDiskCacheEntry; michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheRecord michael@0: * michael@0: * Cache Location Format michael@0: * michael@0: * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit michael@0: * michael@0: * 0011 0000 0000 0000 0000 0000 0000 0000 : File Selector (0 = separate file) michael@0: * 0000 0011 0000 0000 0000 0000 0000 0000 : number of extra contiguous blocks 1-4 michael@0: * 0100 1100 0000 0000 0000 0000 0000 0000 : reserved bits michael@0: * 0000 0000 1111 1111 1111 1111 1111 1111 : block# 0-16777216 (2^24) michael@0: * michael@0: * 0000 0000 1111 1111 1111 1111 0000 0000 : eFileSizeMask (size of file in k: see note) michael@0: * 0000 0000 0000 0000 0000 0000 1111 1111 : eFileGenerationMask michael@0: * michael@0: * File Selector: michael@0: * 0 = separate file on disk michael@0: * 1 = 256 byte block file michael@0: * 2 = 1k block file michael@0: * 3 = 4k block file michael@0: * michael@0: * eFileSizeMask note: Files larger than 65535 KiB have this limit stored in michael@0: * the location. The file itself must be examined to michael@0: * determine its actual size if necessary. michael@0: * michael@0: *****************************************************************************/ michael@0: michael@0: /* michael@0: We have 3 block files with roughly the same max size (32MB) michael@0: 1 - block size 256B, number of blocks 131072 michael@0: 2 - block size 1kB, number of blocks 32768 michael@0: 3 - block size 4kB, number of blocks 8192 michael@0: */ michael@0: #define kNumBlockFiles 3 michael@0: #define SIZE_SHIFT(idx) (2 * ((idx) - 1)) michael@0: #define BLOCK_SIZE_FOR_INDEX(idx) ((idx) ? (256 << SIZE_SHIFT(idx)) : 0) michael@0: #define BITMAP_SIZE_FOR_INDEX(idx) ((idx) ? (131072 >> SIZE_SHIFT(idx)) : 0) michael@0: michael@0: // Min and max values for the number of records in the DiskCachemap michael@0: #define kMinRecordCount 512 michael@0: michael@0: #define kSeparateFile 0 michael@0: #define kBuckets (1 << 5) // must be a power of 2! michael@0: michael@0: // Maximum size in K which can be stored in the location (see eFileSizeMask). michael@0: // Both data and metadata can be larger, but only up to kMaxDataSizeK can be michael@0: // counted into total cache size. I.e. if there are entries where either data or michael@0: // metadata is larger than kMaxDataSizeK, the total cache size will be michael@0: // inaccurate (smaller) than the actual cache size. The alternative is to stat michael@0: // the files to find the real size, which was decided against for performance michael@0: // reasons. See bug #651100 comment #21. michael@0: #define kMaxDataSizeK 0xFFFF michael@0: michael@0: // preallocate up to 1MB of separate cache file michael@0: #define kPreallocateLimit 1 * 1024 * 1024 michael@0: michael@0: // The minimum amount of milliseconds to wait before re-attempting to michael@0: // revalidate the cache. michael@0: #define kRevalidateCacheTimeout 3000 michael@0: #define kRevalidateCacheTimeoutTolerance 10 michael@0: #define kRevalidateCacheErrorTimeout 1000 michael@0: michael@0: class nsDiskCacheRecord { michael@0: michael@0: private: michael@0: uint32_t mHashNumber; michael@0: uint32_t mEvictionRank; michael@0: uint32_t mDataLocation; michael@0: uint32_t mMetaLocation; michael@0: michael@0: enum { michael@0: eLocationInitializedMask = 0x80000000, michael@0: michael@0: eLocationSelectorMask = 0x30000000, michael@0: eLocationSelectorOffset = 28, michael@0: michael@0: eExtraBlocksMask = 0x03000000, michael@0: eExtraBlocksOffset = 24, michael@0: michael@0: eReservedMask = 0x4C000000, michael@0: michael@0: eBlockNumberMask = 0x00FFFFFF, michael@0: michael@0: eFileSizeMask = 0x00FFFF00, michael@0: eFileSizeOffset = 8, michael@0: eFileGenerationMask = 0x000000FF, michael@0: eFileReservedMask = 0x4F000000 michael@0: michael@0: }; michael@0: michael@0: public: michael@0: nsDiskCacheRecord() michael@0: : mHashNumber(0), mEvictionRank(0), mDataLocation(0), mMetaLocation(0) michael@0: { michael@0: } michael@0: michael@0: bool ValidRecord() michael@0: { michael@0: if ((mDataLocation & eReservedMask) || (mMetaLocation & eReservedMask)) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: // HashNumber accessors michael@0: uint32_t HashNumber() const { return mHashNumber; } michael@0: void SetHashNumber( uint32_t hashNumber) { mHashNumber = hashNumber; } michael@0: michael@0: // EvictionRank accessors michael@0: uint32_t EvictionRank() const { return mEvictionRank; } michael@0: void SetEvictionRank( uint32_t rank) { mEvictionRank = rank ? rank : 1; } michael@0: michael@0: // DataLocation accessors michael@0: bool DataLocationInitialized() const { return 0 != (mDataLocation & eLocationInitializedMask); } michael@0: void ClearDataLocation() { mDataLocation = 0; } michael@0: michael@0: uint32_t DataFile() const michael@0: { michael@0: return (uint32_t)(mDataLocation & eLocationSelectorMask) >> eLocationSelectorOffset; michael@0: } michael@0: michael@0: void SetDataBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount) michael@0: { michael@0: // clear everything michael@0: mDataLocation = 0; michael@0: michael@0: // set file index michael@0: NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index"); michael@0: NS_ASSERTION( index > 0,"invalid location index"); michael@0: mDataLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask; michael@0: michael@0: // set startBlock michael@0: NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number"); michael@0: mDataLocation |= startBlock & eBlockNumberMask; michael@0: michael@0: // set blockCount michael@0: NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count"); michael@0: --blockCount; michael@0: mDataLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask; michael@0: michael@0: mDataLocation |= eLocationInitializedMask; michael@0: } michael@0: michael@0: uint32_t DataBlockCount() const michael@0: { michael@0: return (uint32_t)((mDataLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1; michael@0: } michael@0: michael@0: uint32_t DataStartBlock() const michael@0: { michael@0: return (mDataLocation & eBlockNumberMask); michael@0: } michael@0: michael@0: uint32_t DataBlockSize() const michael@0: { michael@0: return BLOCK_SIZE_FOR_INDEX(DataFile()); michael@0: } michael@0: michael@0: uint32_t DataFileSize() const { return (mDataLocation & eFileSizeMask) >> eFileSizeOffset; } michael@0: void SetDataFileSize(uint32_t size) michael@0: { michael@0: NS_ASSERTION((mDataLocation & eFileReservedMask) == 0, "bad location"); michael@0: mDataLocation &= ~eFileSizeMask; // clear eFileSizeMask michael@0: mDataLocation |= (size << eFileSizeOffset) & eFileSizeMask; michael@0: } michael@0: michael@0: uint8_t DataFileGeneration() const michael@0: { michael@0: return (mDataLocation & eFileGenerationMask); michael@0: } michael@0: michael@0: void SetDataFileGeneration( uint8_t generation) michael@0: { michael@0: // clear everything, (separate file index = 0) michael@0: mDataLocation = 0; michael@0: mDataLocation |= generation & eFileGenerationMask; michael@0: mDataLocation |= eLocationInitializedMask; michael@0: } michael@0: michael@0: // MetaLocation accessors michael@0: bool MetaLocationInitialized() const { return 0 != (mMetaLocation & eLocationInitializedMask); } michael@0: void ClearMetaLocation() { mMetaLocation = 0; } michael@0: uint32_t MetaLocation() const { return mMetaLocation; } michael@0: michael@0: uint32_t MetaFile() const michael@0: { michael@0: return (uint32_t)(mMetaLocation & eLocationSelectorMask) >> eLocationSelectorOffset; michael@0: } michael@0: michael@0: void SetMetaBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount) michael@0: { michael@0: // clear everything michael@0: mMetaLocation = 0; michael@0: michael@0: // set file index michael@0: NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index"); michael@0: NS_ASSERTION( index > 0, "invalid location index"); michael@0: mMetaLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask; michael@0: michael@0: // set startBlock michael@0: NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number"); michael@0: mMetaLocation |= startBlock & eBlockNumberMask; michael@0: michael@0: // set blockCount michael@0: NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count"); michael@0: --blockCount; michael@0: mMetaLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask; michael@0: michael@0: mMetaLocation |= eLocationInitializedMask; michael@0: } michael@0: michael@0: uint32_t MetaBlockCount() const michael@0: { michael@0: return (uint32_t)((mMetaLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1; michael@0: } michael@0: michael@0: uint32_t MetaStartBlock() const michael@0: { michael@0: return (mMetaLocation & eBlockNumberMask); michael@0: } michael@0: michael@0: uint32_t MetaBlockSize() const michael@0: { michael@0: return BLOCK_SIZE_FOR_INDEX(MetaFile()); michael@0: } michael@0: michael@0: uint32_t MetaFileSize() const { return (mMetaLocation & eFileSizeMask) >> eFileSizeOffset; } michael@0: void SetMetaFileSize(uint32_t size) michael@0: { michael@0: mMetaLocation &= ~eFileSizeMask; // clear eFileSizeMask michael@0: mMetaLocation |= (size << eFileSizeOffset) & eFileSizeMask; michael@0: } michael@0: michael@0: uint8_t MetaFileGeneration() const michael@0: { michael@0: return (mMetaLocation & eFileGenerationMask); michael@0: } michael@0: michael@0: void SetMetaFileGeneration( uint8_t generation) michael@0: { michael@0: // clear everything, (separate file index = 0) michael@0: mMetaLocation = 0; michael@0: mMetaLocation |= generation & eFileGenerationMask; michael@0: mMetaLocation |= eLocationInitializedMask; michael@0: } michael@0: michael@0: uint8_t Generation() const michael@0: { michael@0: if ((mDataLocation & eLocationInitializedMask) && michael@0: (DataFile() == 0)) michael@0: return DataFileGeneration(); michael@0: michael@0: if ((mMetaLocation & eLocationInitializedMask) && michael@0: (MetaFile() == 0)) michael@0: return MetaFileGeneration(); michael@0: michael@0: return 0; // no generation michael@0: } michael@0: michael@0: #if defined(IS_LITTLE_ENDIAN) michael@0: void Swap() michael@0: { michael@0: mHashNumber = htonl(mHashNumber); michael@0: mEvictionRank = htonl(mEvictionRank); michael@0: mDataLocation = htonl(mDataLocation); michael@0: mMetaLocation = htonl(mMetaLocation); michael@0: } michael@0: #endif michael@0: michael@0: #if defined(IS_LITTLE_ENDIAN) michael@0: void Unswap() michael@0: { michael@0: mHashNumber = ntohl(mHashNumber); michael@0: mEvictionRank = ntohl(mEvictionRank); michael@0: mDataLocation = ntohl(mDataLocation); michael@0: mMetaLocation = ntohl(mMetaLocation); michael@0: } michael@0: #endif michael@0: michael@0: }; michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheRecordVisitor michael@0: *****************************************************************************/ michael@0: michael@0: enum { kDeleteRecordAndContinue = -1, michael@0: kStopVisitingRecords = 0, michael@0: kVisitNextRecord = 1 michael@0: }; michael@0: michael@0: class nsDiskCacheRecordVisitor { michael@0: public: michael@0: michael@0: virtual int32_t VisitRecord( nsDiskCacheRecord * mapRecord) = 0; michael@0: }; michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheHeader michael@0: *****************************************************************************/ michael@0: michael@0: struct nsDiskCacheHeader { michael@0: uint32_t mVersion; // cache version. michael@0: uint32_t mDataSize; // size of cache in units of 1024bytes. michael@0: int32_t mEntryCount; // number of entries stored in cache. michael@0: uint32_t mIsDirty; // dirty flag. michael@0: int32_t mRecordCount; // Number of records michael@0: uint32_t mEvictionRank[kBuckets]; // Highest EvictionRank of the bucket michael@0: uint32_t mBucketUsage[kBuckets]; // Number of used entries in the bucket michael@0: michael@0: nsDiskCacheHeader() michael@0: : mVersion(nsDiskCache::kCurrentVersion) michael@0: , mDataSize(0) michael@0: , mEntryCount(0) michael@0: , mIsDirty(true) michael@0: , mRecordCount(0) michael@0: {} michael@0: michael@0: void Swap() michael@0: { michael@0: #if defined(IS_LITTLE_ENDIAN) michael@0: mVersion = htonl(mVersion); michael@0: mDataSize = htonl(mDataSize); michael@0: mEntryCount = htonl(mEntryCount); michael@0: mIsDirty = htonl(mIsDirty); michael@0: mRecordCount = htonl(mRecordCount); michael@0: michael@0: for (uint32_t i = 0; i < kBuckets ; i++) { michael@0: mEvictionRank[i] = htonl(mEvictionRank[i]); michael@0: mBucketUsage[i] = htonl(mBucketUsage[i]); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void Unswap() michael@0: { michael@0: #if defined(IS_LITTLE_ENDIAN) michael@0: mVersion = ntohl(mVersion); michael@0: mDataSize = ntohl(mDataSize); michael@0: mEntryCount = ntohl(mEntryCount); michael@0: mIsDirty = ntohl(mIsDirty); michael@0: mRecordCount = ntohl(mRecordCount); michael@0: michael@0: for (uint32_t i = 0; i < kBuckets ; i++) { michael@0: mEvictionRank[i] = ntohl(mEvictionRank[i]); michael@0: mBucketUsage[i] = ntohl(mBucketUsage[i]); michael@0: } michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheMap michael@0: *****************************************************************************/ michael@0: michael@0: class nsDiskCacheMap { michael@0: public: michael@0: michael@0: nsDiskCacheMap() : michael@0: mCacheDirectory(nullptr), michael@0: mMapFD(nullptr), michael@0: mCleanFD(nullptr), michael@0: mRecordArray(nullptr), michael@0: mBufferSize(0), michael@0: mBuffer(nullptr), michael@0: mMaxRecordCount(16384), // this default value won't matter michael@0: mIsDirtyCacheFlushed(false), michael@0: mLastInvalidateTime(0) michael@0: { } michael@0: michael@0: ~nsDiskCacheMap() michael@0: { michael@0: (void) Close(true); michael@0: } michael@0: michael@0: /** michael@0: * File Operations michael@0: * michael@0: * Open michael@0: * michael@0: * Creates a new cache map file if one doesn't exist. michael@0: * Returns error if it detects change in format or cache wasn't closed. michael@0: */ michael@0: nsresult Open( nsIFile * cacheDirectory, michael@0: nsDiskCache::CorruptCacheInfo * corruptInfo, michael@0: bool reportCacheCleanTelemetryData); michael@0: nsresult Close(bool flush); michael@0: nsresult Trim(); michael@0: michael@0: nsresult FlushHeader(); michael@0: nsresult FlushRecords( bool unswap); michael@0: michael@0: void NotifyCapacityChange(uint32_t capacity); michael@0: michael@0: /** michael@0: * Record operations michael@0: */ michael@0: nsresult AddRecord( nsDiskCacheRecord * mapRecord, nsDiskCacheRecord * oldRecord); michael@0: nsresult UpdateRecord( nsDiskCacheRecord * mapRecord); michael@0: nsresult FindRecord( uint32_t hashNumber, nsDiskCacheRecord * mapRecord); michael@0: nsresult DeleteRecord( nsDiskCacheRecord * mapRecord); michael@0: nsresult VisitRecords( nsDiskCacheRecordVisitor * visitor); michael@0: nsresult EvictRecords( nsDiskCacheRecordVisitor * visitor); michael@0: michael@0: /** michael@0: * Disk Entry operations michael@0: */ michael@0: nsresult DeleteStorage( nsDiskCacheRecord * record); michael@0: michael@0: nsresult GetFileForDiskCacheRecord( nsDiskCacheRecord * record, michael@0: bool meta, michael@0: bool createPath, michael@0: nsIFile ** result); michael@0: michael@0: nsresult GetLocalFileForDiskCacheRecord( nsDiskCacheRecord * record, michael@0: bool meta, michael@0: bool createPath, michael@0: nsIFile ** result); michael@0: michael@0: // On success, this returns the buffer owned by nsDiskCacheMap, michael@0: // so it must not be deleted by the caller. michael@0: nsDiskCacheEntry * ReadDiskCacheEntry( nsDiskCacheRecord * record); michael@0: michael@0: nsresult WriteDiskCacheEntry( nsDiskCacheBinding * binding); michael@0: michael@0: nsresult ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size); michael@0: nsresult WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size); michael@0: nsresult DeleteStorage( nsDiskCacheRecord * record, bool metaData); michael@0: michael@0: /** michael@0: * Statistical Operations michael@0: */ michael@0: void IncrementTotalSize( uint32_t delta) michael@0: { michael@0: mHeader.mDataSize += delta; michael@0: mHeader.mIsDirty = true; michael@0: } michael@0: michael@0: void DecrementTotalSize( uint32_t delta) michael@0: { michael@0: NS_ASSERTION(mHeader.mDataSize >= delta, "disk cache size negative?"); michael@0: mHeader.mDataSize = mHeader.mDataSize > delta ? mHeader.mDataSize - delta : 0; michael@0: mHeader.mIsDirty = true; michael@0: } michael@0: michael@0: inline void IncrementTotalSize( uint32_t blocks, uint32_t blockSize) michael@0: { michael@0: // Round up to nearest K michael@0: IncrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10); michael@0: } michael@0: michael@0: inline void DecrementTotalSize( uint32_t blocks, uint32_t blockSize) michael@0: { michael@0: // Round up to nearest K michael@0: DecrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10); michael@0: } michael@0: michael@0: uint32_t TotalSize() { return mHeader.mDataSize; } michael@0: michael@0: int32_t EntryCount() { return mHeader.mEntryCount; } michael@0: michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); michael@0: michael@0: michael@0: private: michael@0: michael@0: /** michael@0: * Private methods michael@0: */ michael@0: nsresult OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo); michael@0: nsresult CloseBlockFiles(bool flush); michael@0: bool CacheFilesExist(); michael@0: michael@0: nsresult CreateCacheSubDirectories(); michael@0: michael@0: uint32_t CalculateFileIndex(uint32_t size); michael@0: michael@0: nsresult GetBlockFileForIndex( uint32_t index, nsIFile ** result); michael@0: uint32_t GetBlockSizeForIndex( uint32_t index) const { michael@0: return BLOCK_SIZE_FOR_INDEX(index); michael@0: } michael@0: uint32_t GetBitMapSizeForIndex( uint32_t index) const { michael@0: return BITMAP_SIZE_FOR_INDEX(index); michael@0: } michael@0: michael@0: // returns the bucket number michael@0: uint32_t GetBucketIndex( uint32_t hashNumber) const { michael@0: return (hashNumber & (kBuckets - 1)); michael@0: } michael@0: michael@0: // Gets the size of the bucket (in number of records) michael@0: uint32_t GetRecordsPerBucket() const { michael@0: return mHeader.mRecordCount / kBuckets; michael@0: } michael@0: michael@0: // Gets the first record in the bucket michael@0: nsDiskCacheRecord *GetFirstRecordInBucket(uint32_t bucket) const { michael@0: return mRecordArray + bucket * GetRecordsPerBucket(); michael@0: } michael@0: michael@0: uint32_t GetBucketRank(uint32_t bucketIndex, uint32_t targetRank); michael@0: michael@0: int32_t VisitEachRecord(uint32_t bucketIndex, michael@0: nsDiskCacheRecordVisitor * visitor, michael@0: uint32_t evictionRank); michael@0: michael@0: nsresult GrowRecords(); michael@0: nsresult ShrinkRecords(); michael@0: michael@0: nsresult EnsureBuffer(uint32_t bufSize); michael@0: michael@0: // The returned structure will point to the buffer owned by nsDiskCacheMap, michael@0: // so it must not be deleted by the caller. michael@0: nsDiskCacheEntry * CreateDiskCacheEntry(nsDiskCacheBinding * binding, michael@0: uint32_t * size); michael@0: michael@0: // Initializes the _CACHE_CLEAN_ related functionality michael@0: nsresult InitCacheClean(nsIFile * cacheDirectory, michael@0: nsDiskCache::CorruptCacheInfo * corruptInfo, michael@0: bool reportCacheCleanTelemetryData); michael@0: // Writes out a value of '0' or '1' in the _CACHE_CLEAN_ file michael@0: nsresult WriteCacheClean(bool clean); michael@0: // Resets the timout for revalidating the cache michael@0: nsresult ResetCacheTimer(int32_t timeout = kRevalidateCacheTimeout); michael@0: // Invalidates the cache, calls WriteCacheClean and ResetCacheTimer michael@0: nsresult InvalidateCache(); michael@0: // Determines if the cache is in a safe state michael@0: bool IsCacheInSafeState(); michael@0: // Revalidates the cache by writting out the header, records, and finally michael@0: // by calling WriteCacheClean(true). michael@0: nsresult RevalidateCache(); michael@0: // Timer which revalidates the cache michael@0: static void RevalidateTimerCallback(nsITimer *aTimer, void *arg); michael@0: michael@0: /** michael@0: * data members michael@0: */ michael@0: private: michael@0: nsCOMPtr mCleanCacheTimer; michael@0: nsCOMPtr mCacheDirectory; michael@0: PRFileDesc * mMapFD; michael@0: PRFileDesc * mCleanFD; michael@0: nsDiskCacheRecord * mRecordArray; michael@0: nsDiskCacheBlockFile mBlockFile[kNumBlockFiles]; michael@0: uint32_t mBufferSize; michael@0: char * mBuffer; michael@0: nsDiskCacheHeader mHeader; michael@0: int32_t mMaxRecordCount; michael@0: bool mIsDirtyCacheFlushed; michael@0: PRIntervalTime mLastInvalidateTime; michael@0: }; michael@0: michael@0: #endif // _nsDiskCacheMap_h_