Back to index

lightning-sunbird  0.9+nobinonly
nsDiskCacheMap.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set ts=4 sw=4 sts=4 cin et: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is nsDiskCacheMap.cpp, released
00017  * March 23, 2001.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 2001
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *   Patrick C. Beard <beard@netscape.com>
00026  *   Gordon Sheridan  <gordon@netscape.com>
00027  *   Alfred Kayser <alfredkayser@nl.ibm.com>
00028  *   Darin Fisher <darin@meer.net>
00029  *
00030  * Alternatively, the contents of this file may be used under the terms of
00031  * either the GNU General Public License Version 2 or later (the "GPL"), or
00032  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00033  * in which case the provisions of the GPL or the LGPL are applicable instead
00034  * of those above. If you wish to allow use of your version of this file only
00035  * under the terms of either the GPL or the LGPL, and not to allow others to
00036  * use your version of this file under the terms of the MPL, indicate your
00037  * decision by deleting the provisions above and replace them with the notice
00038  * and other provisions required by the GPL or the LGPL. If you do not delete
00039  * the provisions above, a recipient may use your version of this file under
00040  * the terms of any one of the MPL, the GPL or the LGPL.
00041  *
00042  * ***** END LICENSE BLOCK ***** */
00043 
00044 #include "nsDiskCacheMap.h"
00045 #include "nsDiskCacheBinding.h"
00046 #include "nsDiskCacheEntry.h"
00047 
00048 #include "nsCache.h"
00049 
00050 #include <string.h>
00051 
00052 
00053 /******************************************************************************
00054  *  nsDiskCacheMap
00055  *****************************************************************************/
00056 
00061 nsresult
00062 nsDiskCacheMap::Open(nsILocalFile *  cacheDirectory)
00063 {
00064     NS_ENSURE_ARG_POINTER(cacheDirectory);
00065     if (mMapFD)  return NS_ERROR_ALREADY_INITIALIZED;
00066 
00067     mCacheDirectory = cacheDirectory;   // save a reference for ourselves
00068     
00069     // create nsILocalFile for _CACHE_MAP_
00070     nsresult rv;
00071     nsCOMPtr<nsIFile> file;
00072     rv = cacheDirectory->Clone(getter_AddRefs(file));
00073     nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file, &rv));
00074     if (NS_FAILED(rv))  return rv;
00075     rv = localFile->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
00076     if (NS_FAILED(rv))  return rv;
00077 
00078     // open the file - restricted to user, the data could be confidential
00079     rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
00080     if (NS_FAILED(rv))  return NS_ERROR_FILE_CORRUPTED;
00081 
00082     PRBool cacheFilesExist = CacheFilesExist();
00083     rv = NS_ERROR_FILE_CORRUPTED;  // presume the worst
00084 
00085     // check size of map file
00086     PRUint32 mapSize = PR_Available(mMapFD);    
00087     if (mapSize == 0) {  // creating a new _CACHE_MAP_
00088 
00089         // block files shouldn't exist if we're creating the _CACHE_MAP_
00090         if (cacheFilesExist)
00091             goto error_exit;
00092 
00093         // create the file - initialize in memory
00094         memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
00095         mHeader.mVersion = nsDiskCache::kCurrentVersion;
00096         mHeader.mRecordCount = kMinRecordCount;
00097         mRecordArray = (nsDiskCacheRecord *)
00098             PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
00099         if (!mRecordArray) {
00100             rv = NS_ERROR_OUT_OF_MEMORY;
00101             goto error_exit;
00102         }
00103     } else if (mapSize >= sizeof(nsDiskCacheHeader)) {  // read existing _CACHE_MAP_
00104         
00105         // if _CACHE_MAP_ exists, so should the block files
00106         if (!cacheFilesExist)
00107             goto error_exit;
00108 
00109         // read the header
00110         PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
00111         if (sizeof(nsDiskCacheHeader) != bytesRead)  goto error_exit;
00112         mHeader.Unswap();
00113 
00114         if (mHeader.mIsDirty || (mHeader.mVersion != nsDiskCache::kCurrentVersion))
00115             goto error_exit;
00116 
00117         PRUint32 recordArraySize =
00118                 mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
00119         if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader))
00120             goto error_exit;
00121 
00122         // Get the space for the records
00123         mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
00124         if (!mRecordArray) {
00125             rv = NS_ERROR_OUT_OF_MEMORY;
00126             goto error_exit;
00127         }
00128 
00129         // Read the records
00130         bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
00131         if (bytesRead < recordArraySize)
00132             goto error_exit;
00133 
00134         // Unswap each record
00135         PRInt32 total = 0;
00136         for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
00137             if (mRecordArray[i].HashNumber()) {
00138 #if defined(IS_LITTLE_ENDIAN)
00139                 mRecordArray[i].Unswap();
00140 #endif
00141                 total ++;
00142             }
00143         }
00144         
00145         // verify entry count
00146         if (total != mHeader.mEntryCount)
00147             goto error_exit;
00148 
00149     } else {
00150         goto error_exit;
00151     }
00152 
00153     rv = OpenBlockFiles();
00154     if (NS_FAILED(rv))  goto error_exit;
00155 
00156     // set dirty bit and flush header
00157     mHeader.mIsDirty    = PR_TRUE;
00158     rv = FlushHeader();
00159     if (NS_FAILED(rv))  goto error_exit;
00160     
00161     return NS_OK;
00162     
00163 error_exit:
00164     (void) Close(PR_FALSE);
00165        
00166     return rv;
00167 }
00168 
00169 
00170 nsresult
00171 nsDiskCacheMap::Close(PRBool flush)
00172 {
00173     nsresult  rv = NS_OK;
00174 
00175     // If cache map file and its block files are still open, close them
00176     if (mMapFD) {
00177         // close block files
00178         rv = CloseBlockFiles(flush);
00179         if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
00180             // write the map records
00181             rv = FlushRecords(PR_FALSE);   // don't bother swapping buckets back
00182             if (NS_SUCCEEDED(rv)) {
00183                 // clear dirty bit
00184                 mHeader.mIsDirty = PR_FALSE;
00185                 rv = FlushHeader();
00186             }
00187         }
00188         if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
00189             rv = NS_ERROR_UNEXPECTED;
00190 
00191         mMapFD = nsnull;
00192     }
00193     PR_FREEIF(mRecordArray);
00194     return rv;
00195 }
00196 
00197 
00198 nsresult
00199 nsDiskCacheMap::Trim()
00200 {
00201     nsresult rv, rv2 = NS_OK;
00202     for (int i=0; i < 3; ++i) {
00203         rv = mBlockFile[i].Trim();
00204         if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
00205     }
00206     // Try to shrink the records array
00207     rv = ShrinkRecords();
00208     if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
00209     return rv2;
00210 }
00211 
00212 
00213 nsresult
00214 nsDiskCacheMap::FlushHeader()
00215 {
00216     if (!mMapFD)  return NS_ERROR_NOT_AVAILABLE;
00217     
00218     // seek to beginning of cache map
00219     PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
00220     if (filePos != 0)  return NS_ERROR_UNEXPECTED;
00221     
00222     // write the header
00223     mHeader.Swap();
00224     PRInt32 bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
00225     mHeader.Unswap();
00226     if (sizeof(nsDiskCacheHeader) != bytesWritten) {
00227         return NS_ERROR_UNEXPECTED;
00228     }
00229     
00230     return NS_OK;
00231 }
00232 
00233 
00234 nsresult
00235 nsDiskCacheMap::FlushRecords(PRBool unswap)
00236 {
00237     if (!mMapFD)  return NS_ERROR_NOT_AVAILABLE;
00238     
00239     // seek to beginning of buckets
00240     PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
00241     if (filePos != sizeof(nsDiskCacheHeader))
00242         return NS_ERROR_UNEXPECTED;
00243     
00244 #if defined(IS_LITTLE_ENDIAN)
00245     // Swap each record
00246     for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
00247         if (mRecordArray[i].HashNumber())   
00248             mRecordArray[i].Swap();
00249     }
00250 #endif
00251     
00252     PRInt32 recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
00253 
00254     PRInt32 bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
00255     if (bytesWritten != recordArraySize)
00256         return NS_ERROR_UNEXPECTED;
00257 
00258 #if defined(IS_LITTLE_ENDIAN)
00259     if (unswap) {
00260         // Unswap each record
00261         for (PRInt32 i = 0; i < mHeader.mRecordCount; ++i) {
00262             if (mRecordArray[i].HashNumber())   
00263                 mRecordArray[i].Unswap();
00264         }
00265     }
00266 #endif
00267     
00268     return NS_OK;
00269 }
00270 
00271 
00276 PRUint32
00277 nsDiskCacheMap::GetBucketRank(PRUint32 bucketIndex, PRUint32 targetRank)
00278 {
00279     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00280     PRUint32            rank = 0;
00281 
00282     for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {          
00283         if ((rank < records[i].EvictionRank()) &&
00284             ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
00285                 rank = records[i].EvictionRank();
00286     }
00287     return rank;
00288 }
00289 
00290 nsresult
00291 nsDiskCacheMap::GrowRecords()
00292 {
00293     if (mHeader.mRecordCount >= kMaxRecordCount)
00294         return NS_OK;
00295 
00296     // Resize the record array
00297     PRUint32 newCount = mHeader.mRecordCount << 1;
00298     if (newCount > kMaxRecordCount)
00299         newCount = kMaxRecordCount;
00300     nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
00301             PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
00302     if (!newArray)
00303         return NS_ERROR_OUT_OF_MEMORY;
00304 
00305     // Space out the buckets
00306     PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
00307     PRUint32 newRecordsPerBucket = newCount / kBuckets;
00308     // Work from back to space out each bucket to the new array
00309     for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
00310         // Move bucket
00311         nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
00312         const PRUint32 count = mHeader.mBucketUsage[bucketIndex];
00313         memmove(newRecords,
00314                 newArray + bucketIndex * oldRecordsPerBucket,
00315                 count * sizeof(nsDiskCacheRecord));
00316         // Clear the new empty entries
00317         for (PRUint32 i = count; i < newRecordsPerBucket; ++i)
00318             newRecords[i].SetHashNumber(0);
00319     }
00320 
00321     // Set as the new record array
00322     mRecordArray = newArray;
00323     mHeader.mRecordCount = newCount;
00324     return NS_OK;
00325 }
00326 
00327 nsresult
00328 nsDiskCacheMap::ShrinkRecords()
00329 {
00330     if (mHeader.mRecordCount <= kMinRecordCount)
00331         return NS_OK;
00332 
00333     // Verify if we can shrink the record array: all buckets must be less than
00334     // 1/2 filled
00335     PRUint32 maxUsage = 0, bucketIndex;
00336     for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
00337         if (maxUsage < mHeader.mBucketUsage[bucketIndex])
00338             maxUsage = mHeader.mBucketUsage[bucketIndex];
00339     }
00340     // Determine new bucket size, halve size until maxUsage
00341     PRUint32 oldRecordsPerBucket = GetRecordsPerBucket();
00342     PRUint32 newRecordsPerBucket = oldRecordsPerBucket;
00343     while (maxUsage < (newRecordsPerBucket >> 1))
00344         newRecordsPerBucket >>= 1;
00345     if (newRecordsPerBucket < kMinRecordCount) 
00346         newRecordsPerBucket = kMinRecordCount;
00347     if (newRecordsPerBucket == oldRecordsPerBucket)
00348         return NS_OK;
00349     // Move the buckets close to each other
00350     for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
00351         // Move bucket
00352         memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
00353                 mRecordArray + bucketIndex * oldRecordsPerBucket,
00354                 mHeader.mBucketUsage[bucketIndex] * sizeof(nsDiskCacheRecord));
00355     }
00356 
00357     // Shrink the record array memory block itself
00358     PRUint32 newCount = newRecordsPerBucket * kBuckets;
00359     nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
00360             PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
00361     if (!newArray)
00362         return NS_ERROR_OUT_OF_MEMORY;
00363 
00364     // Set as the new record array
00365     mRecordArray = newArray;
00366     mHeader.mRecordCount = newCount;
00367     return NS_OK;
00368 }
00369 
00370 nsresult
00371 nsDiskCacheMap::AddRecord( nsDiskCacheRecord *  mapRecord,
00372                            nsDiskCacheRecord *  oldRecord)
00373 {
00374     const PRUint32      hashNumber = mapRecord->HashNumber();
00375     const PRUint32      bucketIndex = GetBucketIndex(hashNumber);
00376     const PRUint32      count = mHeader.mBucketUsage[bucketIndex];
00377 
00378     oldRecord->SetHashNumber(0);  // signify no record
00379 
00380     if (count == GetRecordsPerBucket()) {
00381         // Ignore failure to grow the record space, we will then reuse old records
00382         GrowRecords();
00383     }
00384     
00385     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00386     if (count < GetRecordsPerBucket()) {
00387         // stick the new record at the end
00388         records[count] = *mapRecord;
00389         mHeader.mEntryCount++;
00390         mHeader.mBucketUsage[bucketIndex]++;           
00391         if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
00392             mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
00393     } else {
00394         // Find the record with the highest eviction rank
00395         nsDiskCacheRecord * mostEvictable = &records[0];
00396         for (int i = count-1; i > 0; i--) {
00397             if (records[i].EvictionRank() > mostEvictable->EvictionRank())
00398                 mostEvictable = &records[i];
00399         }
00400         *oldRecord     = *mostEvictable;    // i == GetRecordsPerBucket(), so
00401                                             // evict the mostEvictable
00402         *mostEvictable = *mapRecord;        // replace it with the new record
00403         // check if we need to update mostEvictable entry in header
00404         if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
00405             mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
00406         if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex]) 
00407             mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
00408     }
00409 
00410     NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
00411                  "eviction rank out of sync");
00412     return NS_OK;
00413 }
00414 
00415 
00416 nsresult
00417 nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord *  mapRecord)
00418 {
00419     const PRUint32      hashNumber = mapRecord->HashNumber();
00420     const PRUint32      bucketIndex = GetBucketIndex(hashNumber);
00421     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00422 
00423     for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {          
00424         if (records[i].HashNumber() == hashNumber) {
00425             const PRUint32 oldRank = records[i].EvictionRank();
00426 
00427             // stick the new record here            
00428             records[i] = *mapRecord;
00429 
00430             // update eviction rank in header if necessary
00431             if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
00432                 mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
00433             else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
00434                 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
00435 
00436 NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
00437              "eviction rank out of sync");
00438             return NS_OK;
00439         }
00440     }
00441     return NS_ERROR_UNEXPECTED;
00442 }
00443 
00444 
00445 nsresult
00446 nsDiskCacheMap::FindRecord( PRUint32  hashNumber, nsDiskCacheRecord *  result)
00447 {
00448     const PRUint32      bucketIndex = GetBucketIndex(hashNumber);
00449     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00450 
00451     for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {          
00452         if (records[i].HashNumber() == hashNumber) {
00453             *result = records[i];    // copy the record
00454             NS_ASSERTION(result->ValidRecord(), "bad cache map record");
00455             return NS_OK;
00456         }
00457     }
00458     return NS_ERROR_CACHE_KEY_NOT_FOUND;
00459 }
00460 
00461 
00462 nsresult
00463 nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord *  mapRecord)
00464 {
00465     const PRUint32      hashNumber = mapRecord->HashNumber();
00466     const PRUint32      bucketIndex = GetBucketIndex(hashNumber);
00467     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00468     PRUint32            last = mHeader.mBucketUsage[bucketIndex]-1;
00469 
00470     for (int i = last; i >= 0; i--) {          
00471         if (records[i].HashNumber() == hashNumber) {
00472             // found it, now delete it.
00473             PRUint32  evictionRank = records[i].EvictionRank();
00474             NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
00475                          "evictionRank out of sync");
00476             // if not the last record, shift last record into opening
00477             records[i] = records[last];
00478             records[last].SetHashNumber(0); // clear last record
00479             mHeader.mBucketUsage[bucketIndex] = last;
00480             mHeader.mEntryCount--;
00481 
00482             // update eviction rank
00483             PRUint32  bucketIndex = GetBucketIndex(mapRecord->HashNumber());
00484             if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
00485                 mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
00486             }
00487 
00488             NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
00489                          GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
00490             return NS_OK;
00491         }
00492     }
00493     return NS_ERROR_UNEXPECTED;
00494 }
00495 
00496 
00497 PRInt32
00498 nsDiskCacheMap::VisitEachRecord(PRUint32                    bucketIndex,
00499                                 nsDiskCacheRecordVisitor *  visitor,
00500                                 PRUint32                    evictionRank)
00501 {
00502     PRInt32             rv = kVisitNextRecord;
00503     PRUint32            count = mHeader.mBucketUsage[bucketIndex];
00504     nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
00505 
00506     // call visitor for each entry (matching any eviction rank)
00507     for (int i = count-1; i >= 0; i--) {
00508         if (evictionRank > records[i].EvictionRank()) continue;
00509 
00510         rv = visitor->VisitRecord(&records[i]);
00511         if (rv == kStopVisitingRecords) 
00512             break;    // Stop visiting records
00513         
00514         if (rv == kDeleteRecordAndContinue) {
00515             --count;
00516             records[i] = records[count];
00517             records[count].SetHashNumber(0);
00518         }
00519     }
00520 
00521     if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
00522         mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
00523         mHeader.mBucketUsage[bucketIndex] = count;
00524         // recalc eviction rank
00525         mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
00526     }
00527     NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
00528                  GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
00529 
00530     return rv;
00531 }
00532 
00533 
00539 nsresult
00540 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor *  visitor)
00541 {
00542     for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
00543         if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
00544             break;
00545     }   
00546     return NS_OK;
00547 }
00548 
00549 
00555 nsresult
00556 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
00557 {
00558     PRUint32  tempRank[kBuckets];
00559     int       bucketIndex = 0;
00560     
00561     // copy eviction rank array
00562     for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
00563         tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
00564 
00565     // Maximum number of iterations determined by number of records
00566     // as a safety limiter for the loop
00567     for (int n = 0; n < mHeader.mEntryCount; ++n) {
00568     
00569         // find bucket with highest eviction rank
00570         PRUint32    rank  = 0;
00571         for (int i = 0; i < kBuckets; ++i) {
00572             if (rank < tempRank[i]) {
00573                 rank = tempRank[i];
00574                 bucketIndex = i;
00575             }
00576         }
00577         
00578         if (rank == 0) break;  // we've examined all the records
00579 
00580         // visit records in bucket with eviction ranks >= target eviction rank
00581         if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
00582             break;
00583 
00584         // find greatest rank less than 'rank'
00585         tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
00586     }
00587     return NS_OK;
00588 }
00589 
00590 
00591 
00592 nsresult
00593 nsDiskCacheMap::OpenBlockFiles()
00594 {
00595     // create nsILocalFile for block file
00596     nsCOMPtr<nsILocalFile> blockFile;
00597     nsresult rv;
00598     
00599     for (int i = 0; i < 3; ++i) {
00600         rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
00601         if (NS_FAILED(rv))  goto error_exit;
00602     
00603         PRUint32 blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
00604         rv = mBlockFile[i].Open(blockFile, blockSize);
00605         if (NS_FAILED(rv)) goto error_exit;
00606     }
00607     return NS_OK;
00608 
00609 error_exit:
00610     (void)CloseBlockFiles(PR_FALSE); // we already have an error to report
00611     return rv;
00612 }
00613 
00614 
00615 nsresult
00616 nsDiskCacheMap::CloseBlockFiles(PRBool flush)
00617 {
00618     nsresult rv, rv2 = NS_OK;
00619     for (int i=0; i < 3; ++i) {
00620         rv = mBlockFile[i].Close(flush);
00621         if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
00622     }
00623     return rv2;
00624 }
00625 
00626 
00627 PRBool
00628 nsDiskCacheMap::CacheFilesExist()
00629 {
00630     nsCOMPtr<nsILocalFile> blockFile;
00631     nsresult rv;
00632     
00633     for (int i = 0; i < 3; ++i) {
00634         PRBool exists;
00635         rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
00636         if (NS_FAILED(rv))  return PR_FALSE;
00637 
00638         rv = blockFile->Exists(&exists);
00639         if (NS_FAILED(rv) || !exists)  return PR_FALSE;
00640     }
00641 
00642     return PR_TRUE;
00643 }
00644 
00645 
00646 nsresult
00647 nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record, nsDiskCacheEntry ** result)
00648 {
00649     nsresult            rv         = NS_ERROR_UNEXPECTED;
00650     nsDiskCacheEntry *  diskEntry  = nsnull;
00651     PRUint32            metaFile   = record->MetaFile();
00652     PRFileDesc *        fd         = nsnull;
00653     *result = nsnull;
00654     
00655     if (!record->MetaLocationInitialized())  return NS_ERROR_NOT_AVAILABLE;
00656     
00657     if (metaFile == 0) {  // entry/metadata stored in separate file
00658         // open and read the file
00659         nsCOMPtr<nsILocalFile> file;
00660         rv = GetLocalFileForDiskCacheRecord(record, nsDiskCache::kMetaData, getter_AddRefs(file));
00661         if (NS_FAILED(rv))  return rv;
00662 
00663         PRFileDesc * fd = nsnull;
00664         // open the file - restricted to user, the data could be confidential
00665         rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
00666         if (NS_FAILED(rv))  return rv;
00667         
00668         PRInt32 fileSize = PR_Available(fd);
00669         if (fileSize < 0) {
00670             // an error occurred. We could call PR_GetError(), but how would that help?
00671             rv = NS_ERROR_UNEXPECTED;
00672             goto exit;
00673         }
00674 
00675         diskEntry = (nsDiskCacheEntry *) new char[fileSize];
00676         if (!diskEntry) {
00677             rv = NS_ERROR_OUT_OF_MEMORY;
00678             goto exit;
00679         }
00680         
00681         PRInt32 bytesRead = PR_Read(fd, diskEntry, fileSize);
00682         if (bytesRead < fileSize) {
00683             rv = NS_ERROR_UNEXPECTED;
00684             goto exit;
00685         }
00686 
00687     } else if (metaFile < 4) {  // XXX magic number: use constant
00688         // entry/metadata stored in cache block file
00689         
00690         // allocate buffer
00691         PRUint32 blockSize  = GetBlockSizeForIndex(metaFile);
00692         PRUint32 blockCount = record->MetaBlockCount();
00693         diskEntry = (nsDiskCacheEntry *) new char[blockSize * blockCount];
00694         
00695         // read diskEntry
00696         rv = mBlockFile[metaFile - 1].ReadBlocks((char *)diskEntry,
00697                                                  record->MetaStartBlock(),
00698                                                  blockCount);
00699         if (NS_FAILED(rv))  goto exit;
00700     }
00701     
00702     diskEntry->Unswap();    // disk to memory
00703     // pass ownership to caller
00704     *result = diskEntry;
00705     diskEntry = nsnull;
00706 
00707 exit:
00708     // XXX auto ptr would be nice
00709     if (fd) (void) PR_Close(fd);
00710     delete [] (char *)diskEntry;
00711     return rv;
00712 }
00713 
00714 
00715 nsresult
00716 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding *  binding)
00717 {
00718     nsresult            rv        = NS_OK;
00719     nsDiskCacheEntry *  diskEntry =  CreateDiskCacheEntry(binding);
00720     if (!diskEntry)  return NS_ERROR_UNEXPECTED;
00721     
00722     PRUint32  size      = diskEntry->Size();
00723     PRUint32  fileIndex = CalculateFileIndex(size);
00724 
00725     // Deallocate old storage if necessary    
00726     if (binding->mRecord.MetaLocationInitialized()) {
00727         // we have existing storage
00728 
00729         if ((binding->mRecord.MetaFile() == 0) &&
00730             (fileIndex == 0)) {  // keeping the separate file
00731             // just decrement total
00732             // XXX if bindRecord.MetaFileSize == USHRT_MAX, stat the file to see how big it is
00733             DecrementTotalSize(binding->mRecord.MetaFileSize() * 1024);
00734             NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
00735                          "generations out of sync");
00736         } else {
00737             rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
00738             if (NS_FAILED(rv))  goto exit;
00739         }
00740     }
00741 
00742     binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
00743         
00744     if (fileIndex == 0) {
00745         // Write entry data to separate file
00746         PRUint32 metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
00747         nsCOMPtr<nsILocalFile> localFile;
00748         
00749         // XXX handle metaFileSizeK > USHRT_MAX
00750         binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
00751         binding->mRecord.SetMetaFileSize(metaFileSizeK);
00752         rv = UpdateRecord(&binding->mRecord);
00753         if (NS_FAILED(rv))  goto exit;
00754 
00755         rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
00756                                             nsDiskCache::kMetaData,
00757                                             getter_AddRefs(localFile));
00758         if (NS_FAILED(rv))  goto exit;
00759         
00760         // open the file
00761         PRFileDesc * fd;
00762         // open the file - restricted to user, the data could be confidential
00763         rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
00764         if (NS_FAILED(rv))  goto exit;  // unable to open or create file
00765 
00766         // write the file
00767         diskEntry->Swap();
00768         PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
00769         
00770         PRStatus err = PR_Close(mMapFD);
00771         if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
00772             rv = NS_ERROR_UNEXPECTED;
00773             goto exit;
00774         }
00775         // XXX handle metaFileSizeK == USHRT_MAX
00776         IncrementTotalSize(metaFileSizeK * 1024);
00777         
00778     } else {
00779         PRUint32  blockSize = GetBlockSizeForIndex(fileIndex);
00780         PRUint32  blocks    = ((size - 1) / blockSize) + 1;
00781 
00782         // write entry data to disk cache block file
00783         PRInt32 startBlock = mBlockFile[fileIndex - 1].AllocateBlocks(blocks);
00784         if (startBlock < 0) {
00785             rv = NS_ERROR_UNEXPECTED;
00786             goto exit;
00787         }
00788         
00789         // update binding and cache map record
00790         binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
00791         rv = UpdateRecord(&binding->mRecord);
00792         if (NS_FAILED(rv))  goto exit;
00793         // XXX we should probably write out bucket ourselves
00794 
00795         // write data
00796         diskEntry->Swap();
00797         rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, startBlock, blocks);
00798         if (NS_FAILED(rv))  goto exit;
00799         
00800         IncrementTotalSize(blocks * blockSize);
00801     }
00802 
00803 exit:
00804     delete [] (char *)diskEntry;
00805     return rv;
00806 }
00807 
00808 
00809 nsresult
00810 nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
00811 {
00812     nsresult  rv;
00813     PRUint32  fileIndex = binding->mRecord.DataFile();
00814     PRUint32  blockSize = GetBlockSizeForIndex(fileIndex);
00815     PRUint32  blockCount = binding->mRecord.DataBlockCount();
00816     PRUint32  minSize = blockSize * blockCount;
00817     
00818     if (size < minSize) {
00819         NS_WARNING("buffer too small");
00820         return NS_ERROR_UNEXPECTED;
00821     }
00822     
00823     rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
00824                                               binding->mRecord.DataStartBlock(),
00825                                               blockCount);
00826     return rv;
00827 }
00828 
00829 
00830 nsresult
00831 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, PRUint32 size)
00832 {
00833     nsresult  rv;
00834     
00835     // determine block file & number of blocks
00836     PRUint32  fileIndex  = CalculateFileIndex(size);
00837     PRUint32  blockSize  = GetBlockSizeForIndex(fileIndex);
00838     PRUint32  blockCount = 0;
00839     PRInt32   startBlock = 0;
00840     
00841     if (size > 0) {
00842         blockCount = ((size - 1) / blockSize) + 1;
00843         startBlock = mBlockFile[fileIndex - 1].AllocateBlocks(blockCount);
00844 
00845         rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, startBlock, blockCount);
00846         if (NS_FAILED(rv))  return rv;
00847         
00848         IncrementTotalSize(blockCount * blockSize);
00849     }
00850     
00851     
00852     // update binding and cache map record
00853     binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
00854     rv = UpdateRecord(&binding->mRecord);
00855 
00856     return rv;
00857 }
00858 
00859 
00860 nsresult
00861 nsDiskCacheMap::DoomRecord(nsDiskCacheRecord * record)
00862 {
00863     nsresult  rv = DeleteRecord(record);
00864     // XXX future: add record to doomed record journal
00865 
00866     return rv;
00867 }
00868 
00869 
00870 nsresult
00871 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
00872 {
00873     nsresult  rv1 = DeleteStorage(record, nsDiskCache::kData);
00874     nsresult  rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
00875     return NS_FAILED(rv1) ? rv1 : rv2;
00876 }
00877 
00878 
00879 nsresult
00880 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, PRBool metaData)
00881 {
00882     nsresult    rv = NS_ERROR_UNEXPECTED;
00883     PRUint32    fileIndex = metaData ? record->MetaFile() : record->DataFile();
00884     nsCOMPtr<nsIFile> file;
00885     
00886     if (fileIndex == 0) {
00887         // delete the file
00888         PRUint32  sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
00889         // XXX if sizeK == USHRT_MAX, stat file for actual size
00890 
00891         rv = GetFileForDiskCacheRecord(record, metaData, getter_AddRefs(file));
00892         if (NS_SUCCEEDED(rv)) {
00893             rv = file->Remove(PR_FALSE);    // false == non-recursive
00894         }
00895         DecrementTotalSize(sizeK * 1024);
00896         
00897     } else if (fileIndex < 4) {
00898         // deallocate blocks
00899         PRInt32  startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
00900         PRInt32  blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
00901         
00902         rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
00903         DecrementTotalSize(blockCount * GetBlockSizeForIndex(fileIndex));
00904     }
00905     if (metaData)  record->ClearMetaLocation();
00906     else           record->ClearDataLocation();
00907     
00908     return rv;
00909 }
00910 
00911 
00912 nsresult
00913 nsDiskCacheMap::DeleteRecordAndStorage(nsDiskCacheRecord * record)
00914 {
00915     nsresult  rv1 = DeleteStorage(record);
00916     nsresult  rv2 = DeleteRecord(record);
00917     return NS_FAILED(rv1) ? rv1 : rv2;
00918 }
00919 
00920 
00921 nsresult
00922 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
00923                                           PRBool              meta,
00924                                           nsIFile **          result)
00925 {
00926     if (!mCacheDirectory)  return NS_ERROR_NOT_AVAILABLE;
00927     
00928     nsCOMPtr<nsIFile> file;
00929     nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
00930     if (NS_FAILED(rv))  return rv;
00931     
00932     PRInt16 generation = record->Generation();
00933     char name[32];
00934     ::sprintf(name, "%08X%c%02X", record->HashNumber(),  (meta ? 'm' : 'd'), generation);
00935     rv = file->AppendNative(nsDependentCString(name));
00936     if (NS_FAILED(rv))  return rv;
00937     
00938     NS_IF_ADDREF(*result = file);
00939     return rv;
00940 }
00941 
00942 
00943 nsresult
00944 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
00945                                                PRBool              meta,
00946                                                nsILocalFile **     result)
00947 {
00948     nsCOMPtr<nsIFile> file;
00949     nsresult rv = GetFileForDiskCacheRecord(record, meta, getter_AddRefs(file));
00950     if (NS_FAILED(rv))  return rv;
00951     
00952     nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
00953     if (NS_FAILED(rv))  return rv;
00954     
00955     NS_IF_ADDREF(*result = localFile);
00956     return rv;
00957 }
00958 
00959 
00960 nsresult
00961 nsDiskCacheMap::GetBlockFileForIndex(PRUint32 index, nsILocalFile ** result)
00962 {
00963     if (!mCacheDirectory)  return NS_ERROR_NOT_AVAILABLE;
00964     
00965     nsCOMPtr<nsIFile> file;
00966     nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
00967     if (NS_FAILED(rv))  return rv;
00968     
00969     char name[32];
00970     ::sprintf(name, "_CACHE_%03d_", index + 1);
00971     rv = file->AppendNative(nsDependentCString(name));
00972     if (NS_FAILED(rv))  return rv;
00973     
00974     nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
00975     NS_IF_ADDREF(*result = localFile);
00976 
00977     return rv;
00978 }
00979 
00980 
00981 PRUint32
00982 nsDiskCacheMap::CalculateFileIndex(PRUint32 size)
00983 {
00984     if (size <=  1024)  return 1;
00985     if (size <=  4096)  return 2;
00986     if (size <= 16384)  return 3;
00987     return 0;  
00988 }