Back to index

lightning-sunbird  0.9+nobinonly
nsDiskCacheDevice.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
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 nsDiskCacheDevice.cpp, released
00017  * February 22, 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  *   Gordon Sheridan <gordon@netscape.com>
00026  *   Patrick C. Beard <beard@netscape.com>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either the GNU General Public License Version 2 or later (the "GPL"), or
00030  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include <limits.h>
00043 
00044 // include files for ftruncate (or equivalent)
00045 #if defined(XP_UNIX) || defined(XP_BEOS)
00046 #include <unistd.h>
00047 #elif defined(XP_MAC)
00048 #include <Files.h>
00049 #elif defined(XP_WIN)
00050 #include <windows.h>
00051 #elif defined(XP_OS2)
00052 #define INCL_DOSERRORS
00053 #include <os2.h>
00054 #else
00055 // XXX add necessary include file for ftruncate (or equivalent)
00056 #endif
00057 
00058 #include "prtypes.h"
00059 #include "prthread.h"
00060 
00061 #if defined(XP_MAC)
00062 #include "pprio.h"
00063 #else
00064 #include "private/pprio.h"
00065 #endif
00066 
00067 #include "nsDiskCacheDevice.h"
00068 #include "nsDiskCacheEntry.h"
00069 #include "nsDiskCacheMap.h"
00070 #include "nsDiskCacheStreams.h"
00071 
00072 #include "nsDiskCache.h"
00073 
00074 #include "nsCacheService.h"
00075 #include "nsCache.h"
00076 
00077 #include "nsDeleteDir.h"
00078 
00079 #include "nsICacheVisitor.h"
00080 #include "nsReadableUtils.h"
00081 #include "nsIInputStream.h"
00082 #include "nsIOutputStream.h"
00083 #include "nsAutoLock.h"
00084 #include "nsCRT.h"
00085 #include "nsCOMArray.h"
00086 #include "nsISimpleEnumerator.h"
00087 
00088 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
00089 
00090 
00091 /******************************************************************************
00092  *  nsDiskCacheEvictor
00093  *
00094  *  Helper class for nsDiskCacheDevice.
00095  *
00096  *****************************************************************************/
00097 #ifdef XP_MAC
00098 #pragma mark -
00099 #pragma mark nsDiskCacheEvictor
00100 #endif
00101 
00102 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
00103 {
00104 public:
00105     nsDiskCacheEvictor( nsDiskCacheDevice *   device,
00106                         nsDiskCacheMap *      cacheMap,
00107                         nsDiskCacheBindery *  cacheBindery,
00108                         PRInt32               targetSize,
00109                         const char *          clientID)
00110         : mDevice(device)
00111         , mCacheMap(cacheMap)
00112         , mBindery(cacheBindery)
00113         , mTargetSize(targetSize)
00114         , mClientID(clientID)
00115     {}
00116     
00117     virtual PRInt32  VisitRecord(nsDiskCacheRecord *  mapRecord);
00118  
00119 private:
00120         nsDiskCacheDevice *  mDevice;
00121         nsDiskCacheMap *     mCacheMap;
00122         nsDiskCacheBindery * mBindery;
00123         PRInt32              mTargetSize;
00124         const char *         mClientID;
00125 };
00126 
00127 
00128 PRInt32
00129 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord *  mapRecord)
00130 {
00131     // read disk cache entry
00132     nsDiskCacheEntry *   diskEntry = nsnull;
00133     nsDiskCacheBinding * binding   = nsnull;
00134     char *               clientID  = nsnull;
00135     PRInt32              result    = kVisitNextRecord;
00136 
00137     if (mClientID) {
00138          // we're just evicting records for a specific client
00139         nsresult  rv = mCacheMap->ReadDiskCacheEntry(mapRecord, &diskEntry);
00140         if (NS_FAILED(rv))  goto exit;  // XXX or delete record?
00141     
00142         // XXX FIXME compare clientID's without malloc
00143 
00144         // get client ID from key
00145         rv = ClientIDFromCacheKey(nsDependentCString(diskEntry->mKeyStart), &clientID);
00146         if (NS_FAILED(rv))  goto exit;
00147          
00148         if (nsCRT::strcmp(mClientID, clientID) != 0) goto exit;
00149     }
00150     
00151     if (mCacheMap->TotalSize() < mTargetSize) {
00152         result = kStopVisitingRecords;
00153         goto exit;
00154     }
00155     
00156     binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
00157     if (binding) {
00158         // We are currently using this entry, so all we can do is doom it.
00159         // Since we're enumerating the records, we don't want to call
00160         // DeleteRecord when nsCacheService::DoomEntry() calls us back.
00161         binding->mDoomed = PR_TRUE;         // mark binding record as 'deleted'
00162         nsCacheService::DoomEntry(binding->mCacheEntry);
00163         result = kDeleteRecordAndContinue;  // this will REALLY delete the record
00164 
00165     } else {
00166         // entry not in use, just delete storage because we're enumerating the records
00167         (void) mCacheMap->DeleteStorage(mapRecord);
00168         result = kDeleteRecordAndContinue;  // this will REALLY delete the record
00169     }
00170 
00171 exit:
00172     
00173     delete clientID;
00174     delete [] (char *)diskEntry;
00175     return result;
00176 }
00177 
00178 
00179 /******************************************************************************
00180  *  nsDiskCacheDeviceInfo
00181  *****************************************************************************/
00182 #ifdef XP_MAC
00183 #pragma mark -
00184 #pragma mark nsDiskCacheDeviceInfo
00185 #endif
00186 
00187 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
00188 public:
00189     NS_DECL_ISUPPORTS
00190     NS_DECL_NSICACHEDEVICEINFO
00191 
00192     nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
00193         :   mDevice(device)
00194     {
00195     }
00196 
00197     virtual ~nsDiskCacheDeviceInfo() {}
00198     
00199 private:
00200     nsDiskCacheDevice* mDevice;
00201 };
00202 
00203 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
00204 
00205 /* readonly attribute string description; */
00206 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
00207 {
00208     NS_ENSURE_ARG_POINTER(aDescription);
00209     *aDescription = nsCRT::strdup("Disk cache device");
00210     return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00211 }
00212 
00213 /* readonly attribute string usageReport; */
00214 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
00215 {
00216     NS_ENSURE_ARG_POINTER(usageReport);
00217     nsCString buffer;
00218     
00219     buffer.AssignLiteral("\n<tr>\n<td><b>Cache Directory:</b></td>\n<td><tt> ");
00220     nsCOMPtr<nsILocalFile> cacheDir;
00221     nsAutoString           path;
00222     mDevice->getCacheDirectory(getter_AddRefs(cacheDir)); 
00223     nsresult rv = cacheDir->GetPath(path);
00224     if (NS_SUCCEEDED(rv)) {
00225         AppendUTF16toUTF8(path, buffer);
00226     } else {
00227         buffer.AppendLiteral("directory unavailable");
00228     }
00229     buffer.AppendLiteral("</tt></td>\n</tr>\n");
00230     // buffer.Append("<tr><td><b>Files:</b></td><td><tt> XXX</tt></td></tr>");
00231     *usageReport = ToNewCString(buffer);
00232     if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
00233 
00234     return NS_OK;
00235 }
00236 
00237 /* readonly attribute unsigned long entryCount; */
00238 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
00239 {
00240     NS_ENSURE_ARG_POINTER(aEntryCount);
00241     *aEntryCount = mDevice->getEntryCount();
00242     return NS_OK;
00243 }
00244 
00245 /* readonly attribute unsigned long totalSize; */
00246 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
00247 {
00248     NS_ENSURE_ARG_POINTER(aTotalSize);
00249     *aTotalSize = mDevice->getCacheSize();
00250     return NS_OK;
00251 }
00252 
00253 /* readonly attribute unsigned long maximumSize; */
00254 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
00255 {
00256     NS_ENSURE_ARG_POINTER(aMaximumSize);
00257     *aMaximumSize = mDevice->getCacheCapacity();
00258     return NS_OK;
00259 }
00260 
00261 
00262 /******************************************************************************
00263  *  nsDiskCache
00264  *****************************************************************************/
00265 #ifdef XP_MAC
00266 #pragma mark -
00267 #pragma mark nsDiskCache
00268 #endif
00269 
00278 PLDHashNumber
00279 nsDiskCache::Hash(const char * key)
00280 {
00281     PLDHashNumber h = 0;
00282     for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s)
00283         h = (h >> (PL_DHASH_BITS - 4)) ^ (h << 4) ^ *s;
00284     return (h == 0 ? ULONG_MAX : h);
00285 }
00286 
00287 
00288 nsresult
00289 nsDiskCache::Truncate(PRFileDesc *  fd, PRUint32  newEOF)
00290 {
00291     // use modified SetEOF from nsFileStreams::SetEOF()
00292 
00293 #if defined(XP_UNIX) || defined(XP_BEOS)
00294     if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
00295         NS_ERROR("ftruncate failed");
00296         return NS_ERROR_FAILURE;
00297     }
00298 
00299 #elif defined(XP_MAC)
00300     if (::SetEOF(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
00301         NS_ERROR("SetEOF failed");
00302         return NS_ERROR_FAILURE;
00303     }
00304 
00305 #elif defined(XP_WIN)
00306     PRInt32 cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
00307     if (cnt == -1)  return NS_ERROR_FAILURE;
00308     if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
00309         NS_ERROR("SetEndOfFile failed");
00310         return NS_ERROR_FAILURE;
00311     }
00312 
00313 #elif defined(XP_OS2)
00314     if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(fd), newEOF) != NO_ERROR) {
00315         NS_ERROR("DosSetFileSize failed");
00316         return NS_ERROR_FAILURE;
00317     }
00318 #else
00319     // add implementations for other platforms here
00320 #endif
00321     return NS_OK;
00322 }
00323 
00324 
00325 /******************************************************************************
00326  *  nsDiskCacheDevice
00327  *****************************************************************************/
00328 
00329 #ifdef XP_MAC
00330 #pragma mark -
00331 #pragma mark nsDiskCacheDevice
00332 #endif
00333 
00334 nsDiskCacheDevice::nsDiskCacheDevice()
00335     : mCacheCapacity(0)
00336     , mCacheMap(nsnull)
00337     , mInitialized(PR_FALSE)
00338 {
00339 }
00340 
00341 nsDiskCacheDevice::~nsDiskCacheDevice()
00342 {
00343     Shutdown();
00344     delete mCacheMap;
00345 }
00346 
00347 
00351 nsresult
00352 nsDiskCacheDevice::Init()
00353 {
00354     nsresult rv;
00355 
00356     NS_ENSURE_TRUE(!Initialized(), NS_ERROR_FAILURE);
00357        
00358     if (!mCacheDirectory) return NS_ERROR_FAILURE;
00359     rv = mBindery.Init();
00360     if (NS_FAILED(rv)) return rv;
00361     
00362     // Open Disk Cache
00363     rv = OpenDiskCache();
00364     if (NS_FAILED(rv)) {
00365         goto error_exit;
00366     }
00367 
00368     mInitialized = PR_TRUE;
00369     return NS_OK;
00370 
00371 error_exit:
00372     if (mCacheMap)  {
00373         (void) mCacheMap->Close(PR_FALSE);
00374         delete mCacheMap;
00375         mCacheMap = nsnull;
00376     }
00377 
00378     return rv;
00379 }
00380 
00381 
00385 nsresult
00386 nsDiskCacheDevice::Shutdown()
00387 {
00388     nsresult rv = Shutdown_Private(PR_TRUE);
00389     if (NS_FAILED(rv))
00390         return rv;
00391 
00392     if (mCacheDirectory) {
00393         // delete any trash files left-over before shutting down.
00394         nsCOMPtr<nsIFile> trashDir;
00395         GetTrashDir(mCacheDirectory, &trashDir);
00396         if (trashDir) {
00397             PRBool exists;
00398             if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
00399                 DeleteDir(trashDir, PR_FALSE, PR_TRUE);
00400         }
00401     }
00402 
00403     return NS_OK;
00404 }
00405 
00406 
00407 nsresult
00408 nsDiskCacheDevice::Shutdown_Private(PRBool  flush)
00409 {
00410     if (Initialized()) {
00411         // check cache limits in case we need to evict.
00412         EvictDiskCacheEntries((PRInt32)mCacheCapacity);
00413 
00414         // write out persistent information about the cache.
00415         (void) mCacheMap->Close(flush);
00416         delete mCacheMap;
00417         mCacheMap = nsnull;
00418 
00419         mBindery.Reset();
00420 
00421         mInitialized = PR_FALSE;
00422     }
00423 
00424     return NS_OK;
00425 }
00426 
00427 
00428 const char *
00429 nsDiskCacheDevice::GetDeviceID()
00430 {
00431     return DISK_CACHE_DEVICE_ID;
00432 }
00433 
00434 
00444 nsCacheEntry *
00445 nsDiskCacheDevice::FindEntry(nsCString * key, PRBool *collision)
00446 {
00447     if (!Initialized())  return nsnull;  // NS_ERROR_NOT_INITIALIZED
00448     nsresult                rv;
00449     nsDiskCacheRecord       record;
00450     nsCacheEntry *          entry   = nsnull;
00451     nsDiskCacheBinding *    binding = nsnull;
00452     PLDHashNumber           hashNumber = nsDiskCache::Hash(key->get());
00453 
00454     *collision = PR_FALSE;
00455 
00456 #if DEBUG  /*because we shouldn't be called for active entries */
00457     binding = mBindery.FindActiveBinding(hashNumber);
00458     NS_ASSERTION(!binding, "FindEntry() called for a bound entry.");
00459     binding = nsnull;
00460 #endif
00461     
00462     // lookup hash number in cache map
00463     rv = mCacheMap->FindRecord(hashNumber, &record);
00464     if (NS_FAILED(rv))  return nsnull;  // XXX log error?
00465     
00466     nsDiskCacheEntry * diskEntry;
00467     rv = mCacheMap->ReadDiskCacheEntry(&record, &diskEntry);
00468     if (NS_FAILED(rv))  return nsnull;
00469     
00470     // compare key to be sure
00471     if (nsCRT::strcmp(diskEntry->mKeyStart, key->get()) == 0) {
00472         entry = diskEntry->CreateCacheEntry(this);
00473     } else {
00474         *collision = PR_TRUE;
00475     }
00476     delete [] (char *)diskEntry;
00477     
00478     // If we had a hash collision or CreateCacheEntry failed, return nsnull
00479     if (!entry)  return nsnull;
00480     
00481     binding = mBindery.CreateBinding(entry, &record);
00482     if (!binding) {
00483         delete entry;
00484         return nsnull;
00485     }
00486     
00487     return entry;
00488 }
00489 
00490 
00494 nsresult
00495 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
00496 {
00497     nsresult              rv = NS_OK;
00498     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00499     NS_ASSERTION(binding, "DeactivateEntry: binding == nsnull");
00500     if (!binding)  return NS_ERROR_UNEXPECTED;
00501 
00502     if (entry->IsDoomed()) {
00503         // delete data, entry, record from disk for entry
00504         rv = mCacheMap->DeleteStorage(&binding->mRecord);
00505 
00506     } else {
00507         // save stuff to disk for entry
00508         rv = mCacheMap->WriteDiskCacheEntry(binding);
00509         if (NS_FAILED(rv)) {
00510             // clean up as best we can
00511             (void) mCacheMap->DeleteRecordAndStorage(&binding->mRecord);
00512             binding->mDoomed = PR_TRUE; // record is no longer in cache map
00513         }
00514     }
00515 
00516     mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
00517     delete entry;   // which will release binding
00518     return rv;
00519 }
00520 
00521 
00537 nsresult
00538 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
00539 {
00540     if (!Initialized())  return  NS_ERROR_NOT_INITIALIZED;
00541     nsresult rv = NS_OK;
00542     nsDiskCacheRecord record, oldRecord;
00543     
00544     // create a new record for this entry
00545     record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
00546     record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
00547 
00548     if (!entry->IsDoomed()) {
00549         // if entry isn't doomed, add it to the cache map
00550         rv = mCacheMap->AddRecord(&record, &oldRecord); // deletes old record, if any
00551         if (NS_FAILED(rv))  return rv;
00552         
00553         PRUint32    oldHashNumber = oldRecord.HashNumber();
00554         if (oldHashNumber) {
00555             // gotta evict this one first
00556             nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
00557             if (oldBinding) {
00558                 // XXX if debug : compare keys for hashNumber collision
00559 
00560                 if (!oldBinding->mCacheEntry->IsDoomed()) {
00561                 // we've got a live one!
00562                     nsCacheService::DoomEntry(oldBinding->mCacheEntry);
00563                     // storage will be delete when oldBinding->mCacheEntry is Deactivated
00564                 }
00565             } else {
00566                 // delete storage
00567                 // XXX if debug : compare keys for hashNumber collision
00568                 rv = mCacheMap->DeleteStorage(&oldRecord);
00569                 if (NS_FAILED(rv))  return rv;  // XXX delete record we just added?
00570             }
00571         }
00572     }
00573     
00574     // Make sure this entry has its associated nsDiskCacheBinding attached.
00575     nsDiskCacheBinding *  binding = mBindery.CreateBinding(entry, &record);
00576     NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
00577     if (!binding) return NS_ERROR_OUT_OF_MEMORY;
00578     NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
00579 
00580     return NS_OK;
00581 }
00582 
00583 
00587 void
00588 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
00589 {
00590     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00591     NS_ASSERTION(binding, "DoomEntry: binding == nsnull");
00592     if (!binding)  return;
00593 
00594     if (!binding->mDoomed) {
00595         // so it can't be seen by FindEntry() ever again.
00596         nsresult rv = mCacheMap->DoomRecord(&binding->mRecord);
00597         NS_ASSERTION(NS_SUCCEEDED(rv),"DoomRecord failed.");
00598         binding->mDoomed = PR_TRUE; // record in no longer in cache map
00599     }
00600 }
00601 
00602 
00606 nsresult
00607 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry *      entry,
00608                                            nsCacheAccessMode   mode, 
00609                                            PRUint32            offset,
00610                                            nsIInputStream **   result)
00611 {
00612     NS_ENSURE_ARG_POINTER(entry);
00613     NS_ENSURE_ARG_POINTER(result);
00614 
00615     nsresult             rv;
00616     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00617     NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
00618     
00619     NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
00620 
00621     rv = binding->EnsureStreamIO();
00622     if (NS_FAILED(rv)) return rv;
00623 
00624     return binding->mStreamIO->GetInputStream(offset, result);
00625 }
00626 
00627 
00631 nsresult
00632 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *      entry,
00633                                             nsCacheAccessMode   mode, 
00634                                             PRUint32            offset,
00635                                             nsIOutputStream **  result)
00636 {
00637     NS_ENSURE_ARG_POINTER(entry);
00638     NS_ENSURE_ARG_POINTER(result);
00639 
00640     nsresult             rv;
00641     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00642     NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
00643     
00644     NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
00645 
00646     rv = binding->EnsureStreamIO();
00647     if (NS_FAILED(rv)) return rv;
00648 
00649     return binding->mStreamIO->GetOutputStream(offset, result);
00650 }
00651 
00652 
00656 nsresult
00657 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry *    entry,
00658                                    nsIFile **        result)
00659 {
00660     NS_ENSURE_ARG_POINTER(result);
00661     *result = nsnull;
00662 
00663     nsresult             rv;
00664         
00665     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00666     if (!binding) {
00667         NS_WARNING("GetFileForEntry: binding == nsnull");
00668         return NS_ERROR_UNEXPECTED;
00669     }
00670     
00671     // check/set binding->mRecord for separate file, sync w/mCacheMap
00672     if (binding->mRecord.DataLocationInitialized()) {
00673         if (binding->mRecord.DataFile() != 0)
00674             return NS_ERROR_NOT_AVAILABLE;  // data not stored as separate file
00675 
00676         NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
00677     } else {
00678         binding->mRecord.SetDataFileGeneration(binding->mGeneration);
00679         binding->mRecord.SetDataFileSize(0);    // 1k minimum
00680         if (!binding->mDoomed) {
00681             // record stored in cache map, so update it
00682             rv = mCacheMap->UpdateRecord(&binding->mRecord);
00683             if (NS_FAILED(rv))  return rv;
00684         }
00685     }
00686     
00687     nsCOMPtr<nsIFile>  file;
00688     rv = mCacheMap->GetFileForDiskCacheRecord(&binding->mRecord,
00689                                               nsDiskCache::kData,
00690                                               getter_AddRefs(file));
00691     if (NS_FAILED(rv))  return rv;
00692     
00693     NS_IF_ADDREF(*result = file);
00694     return NS_OK;
00695 }
00696 
00697 
00703 nsresult
00704 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
00705 {
00706     nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
00707     NS_ASSERTION(binding, "OnDataSizeChange: binding == nsnull");
00708     if (!binding)  return NS_ERROR_UNEXPECTED;
00709 
00710     NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
00711 
00712     PRUint32  newSize = entry->DataSize() + deltaSize;
00713     PRUint32  maxSize = PR_MIN(mCacheCapacity / 2, kMaxDataFileSize);
00714     if (newSize > maxSize) {
00715         nsresult rv = nsCacheService::DoomEntry(entry);
00716         NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
00717         return NS_ERROR_ABORT;
00718     }
00719 
00720     PRUint32  sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
00721     PRUint32  newSizeK =  ((newSize + 0x3FF) >> 10);
00722 
00723     NS_ASSERTION(sizeK < USHRT_MAX, "data size out of range");
00724     NS_ASSERTION(newSizeK < USHRT_MAX, "data size out of range");
00725 
00726     // pre-evict entries to make space for new data
00727     PRInt32  targetCapacity = (PRInt32)(mCacheCapacity - ((newSizeK - sizeK) * 1024));
00728     EvictDiskCacheEntries(targetCapacity);
00729     
00730     return NS_OK;
00731 }
00732 
00733 
00734 /******************************************************************************
00735  *  EntryInfoVisitor
00736  *****************************************************************************/
00737 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
00738 {
00739 public:
00740     EntryInfoVisitor(nsDiskCacheDevice * device,
00741                      nsDiskCacheMap *    cacheMap,
00742                      nsICacheVisitor *   visitor)
00743         : mDevice(device)
00744         , mCacheMap(cacheMap)
00745         , mVisitor(visitor)
00746         , mResult(NS_OK)
00747     {}
00748     
00749     virtual PRInt32  VisitRecord(nsDiskCacheRecord *  mapRecord)
00750     {
00751         // XXX optimization: do we have this record in memory?
00752         
00753         // read in the entry (metadata)
00754         nsDiskCacheEntry * diskEntry;
00755         nsresult rv = mCacheMap->ReadDiskCacheEntry(mapRecord, &diskEntry);
00756         if (NS_FAILED(rv)) {
00757             mResult = rv;
00758             return kVisitNextRecord;
00759         }
00760 
00761         // create nsICacheEntryInfo
00762         nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
00763         if (!entryInfo) {
00764             mResult = NS_ERROR_OUT_OF_MEMORY;
00765             return kStopVisitingRecords;
00766         }
00767         nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
00768         
00769         PRBool  keepGoing;
00770         rv = mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
00771         delete [] (char *)diskEntry;
00772         return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
00773     }
00774  
00775 private:
00776         nsDiskCacheDevice * mDevice;
00777         nsDiskCacheMap *    mCacheMap;
00778         nsICacheVisitor *   mVisitor;
00779         nsresult            mResult;
00780 };
00781 
00782 
00783 nsresult
00784 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
00785 {
00786     if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
00787     nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
00788     nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
00789     
00790     PRBool keepGoing;
00791     nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
00792     if (NS_FAILED(rv)) return rv;
00793     
00794     if (keepGoing) {
00795         EntryInfoVisitor  infoVisitor(this, mCacheMap, visitor);
00796         return mCacheMap->VisitRecords(&infoVisitor);
00797     }
00798 
00799     return NS_OK;
00800 }
00801 
00802 
00803 nsresult
00804 nsDiskCacheDevice::EvictEntries(const char * clientID)
00805 {
00806     if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
00807     nsresult  rv;
00808 
00809     if (clientID == nsnull) {
00810         // we're clearing the entire disk cache
00811         rv = ClearDiskCache();
00812         if (rv != NS_ERROR_CACHE_IN_USE)
00813             return rv;
00814     }
00815 
00816     nsDiskCacheEvictor  evictor(this, mCacheMap, &mBindery, 0, clientID);
00817     rv = mCacheMap->VisitRecords(&evictor);
00818     
00819     if (clientID == nsnull)     // we tried to clear the entire cache
00820         rv = mCacheMap->Trim(); // so trim cache block files (if possible)
00821     return rv;
00822 }
00823 
00824 
00828 #ifdef XP_MAC
00829 #pragma mark -
00830 #pragma mark PRIVATE METHODS
00831 #endif
00832 
00833 
00834 nsresult
00835 nsDiskCacheDevice::OpenDiskCache()
00836 {
00837     nsresult  rv;
00838 
00839     // Try opening cache map file.
00840     NS_ASSERTION(mCacheMap == nsnull, "leaking mCacheMap");
00841     mCacheMap = new nsDiskCacheMap;
00842     if (!mCacheMap)
00843         return NS_ERROR_OUT_OF_MEMORY;
00844     
00845     // if we don't have a cache directory, create one and open it
00846     PRBool exists;
00847     rv = mCacheDirectory->Exists(&exists);
00848     if (NS_FAILED(rv))
00849         return rv;
00850 
00851     PRBool trashing = PR_FALSE;
00852     if (exists) {
00853         rv = mCacheMap->Open(mCacheDirectory);        
00854         // move "corrupt" caches to trash
00855         if (rv == NS_ERROR_FILE_CORRUPTED) {
00856             rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
00857             if (NS_FAILED(rv))
00858                 return rv;
00859             exists = PR_FALSE;
00860             trashing = PR_TRUE;
00861         }
00862         else if (NS_FAILED(rv))
00863             return rv;
00864     }
00865 
00866     // if we don't have a cache directory, create one and open it
00867     if (!exists) {
00868         rv = InitializeCacheDirectory();
00869         if (NS_FAILED(rv))
00870             return rv;
00871     }
00872 
00873     if (!trashing) {
00874         // delete any trash files leftover from a previous run
00875         nsCOMPtr<nsIFile> trashDir;
00876         GetTrashDir(mCacheDirectory, &trashDir);
00877         if (trashDir) {
00878             PRBool exists;
00879             if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
00880                 DeleteDir(trashDir, PR_FALSE, PR_FALSE);
00881         }
00882     }
00883 
00884     return NS_OK;
00885 }
00886 
00887 
00888 nsresult
00889 nsDiskCacheDevice::ClearDiskCache()
00890 {
00891     if (mBindery.ActiveBindings())
00892         return NS_ERROR_CACHE_IN_USE;
00893 
00894     nsresult rv = Shutdown_Private(PR_FALSE);  // false: don't bother flushing
00895     if (NS_FAILED(rv))
00896         return rv;
00897 
00898     // If the disk cache directory is already gone, then it's not an error if
00899     // we fail to delete it ;-)
00900     rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
00901     if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
00902         return rv;
00903 
00904     return Init();
00905 }
00906 
00907 
00908 nsresult
00909 nsDiskCacheDevice::InitializeCacheDirectory()
00910 {
00911     nsresult rv;
00912     
00913     rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
00914     CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
00915     CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
00916     if (NS_FAILED(rv))  return rv;
00917 
00918     // reopen the cache map     
00919     rv = mCacheMap->Open(mCacheDirectory);
00920     return rv;
00921 }
00922 
00923 
00924 nsresult
00925 nsDiskCacheDevice::EvictDiskCacheEntries(PRInt32  targetCapacity)
00926 {
00927     nsresult rv;
00928     
00929     if (mCacheMap->TotalSize() < targetCapacity)  return NS_OK;
00930 
00931     nsDiskCacheEvictor  evictor(this, mCacheMap, &mBindery, targetCapacity, nsnull);
00932     rv = mCacheMap->EvictRecords(&evictor);
00933     
00934     return rv;
00935 }
00936 
00937 
00941 #ifdef XP_MAC
00942 #pragma mark -
00943 #pragma mark PREF METHODS
00944 #endif
00945 
00946 void
00947 nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile * parentDir)
00948 {
00949     nsresult rv;
00950     PRBool  exists;
00951 
00952     if (Initialized()) {
00953         NS_ASSERTION(PR_FALSE, "Cannot switch cache directory when initialized");
00954         return;
00955     }
00956 
00957     if (!parentDir) {
00958         mCacheDirectory = nsnull;
00959         return;
00960     }
00961 
00962     // ensure parent directory exists
00963     rv = parentDir->Exists(&exists);
00964     if (NS_SUCCEEDED(rv) && !exists)
00965         rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
00966     if (NS_FAILED(rv))  return;
00967 
00968     // ensure cache directory exists
00969     nsCOMPtr<nsIFile> directory;
00970     
00971     rv = parentDir->Clone(getter_AddRefs(directory));
00972     if (NS_FAILED(rv))  return;
00973     rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
00974     if (NS_FAILED(rv))  return;
00975     
00976     mCacheDirectory = do_QueryInterface(directory);
00977 }
00978 
00979 
00980 void
00981 nsDiskCacheDevice::getCacheDirectory(nsILocalFile ** result)
00982 {
00983     *result = mCacheDirectory;
00984     NS_IF_ADDREF(*result);
00985 }
00986 
00987 
00991 void
00992 nsDiskCacheDevice::SetCapacity(PRUint32  capacity)
00993 {
00994     mCacheCapacity = capacity * 1024;
00995     if (Initialized()) {
00996         // start evicting entries if the new size is smaller!
00997         EvictDiskCacheEntries((PRInt32)mCacheCapacity);
00998     }
00999 }
01000 
01001 
01002 PRUint32 nsDiskCacheDevice::getCacheCapacity()
01003 {
01004     return mCacheCapacity;
01005 }
01006 
01007 
01008 PRUint32 nsDiskCacheDevice::getCacheSize()
01009 {
01010     return mCacheMap->TotalSize();
01011 }
01012 
01013 
01014 PRUint32 nsDiskCacheDevice::getEntryCount()
01015 {
01016     return mCacheMap->EntryCount();
01017 }