Back to index

lightning-sunbird  0.9+nobinonly
nsDiskCacheDeviceSQL.cpp
Go to the documentation of this file.
00001 /* vim:set ts=2 sw=2 sts=2 et cin: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla.
00016  *
00017  * The Initial Developer of the Original Code is IBM Corporation.
00018  * Portions created by IBM Corporation are Copyright (C) 2004
00019  * IBM Corporation. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Darin Fisher <darin@meer.net>
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 // include files for ftruncate (or equivalent)
00039 #if defined(XP_UNIX) || defined(XP_BEOS)
00040 #include <unistd.h>
00041 #elif defined(XP_MAC)
00042 #include <Files.h>
00043 #elif defined(XP_WIN)
00044 #include <windows.h>
00045 #elif defined(XP_OS2)
00046 #define INCL_DOSERRORS
00047 #include <os2.h>
00048 #else
00049 // XXX add necessary include file for ftruncate (or equivalent)
00050 #endif
00051 
00052 #if defined(XP_MAC)
00053 #include "pprio.h"
00054 #else
00055 #include "private/pprio.h"
00056 #endif
00057 
00058 #include "prlong.h"
00059 
00060 #include "nsCache.h"
00061 #include "nsDiskCache.h"
00062 #include "nsDiskCacheDeviceSQL.h"
00063 
00064 #include "nsNetUtil.h"
00065 #include "nsAutoPtr.h"
00066 #include "nsString.h"
00067 #include "nsPrintfCString.h"
00068 #include "nsCRT.h"
00069 
00070 #include "mozIStorageService.h"
00071 #include "mozIStorageStatement.h"
00072 #include "mozStorageCID.h"
00073 
00074 #include "nsICacheVisitor.h"
00075 #include "nsISeekableStream.h"
00076 
00077 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
00078 
00079 #define LOG(args) CACHE_LOG_DEBUG(args)
00080 
00081 
00082 /*****************************************************************************
00083  * helpers
00084  */
00085 
00086 static nsresult
00087 EnsureDir(nsIFile *dir)
00088 {
00089   PRBool exists;
00090   nsresult rv = dir->Exists(&exists);
00091   if (NS_SUCCEEDED(rv) && !exists)
00092     rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
00093   return rv;
00094 }
00095 
00096 static PRBool
00097 DecomposeCacheEntryKey(const nsCString *fullKey,
00098                        const char **cid,
00099                        const char **key,
00100                        nsCString &buf)
00101 {
00102   buf = *fullKey;
00103 
00104   PRInt32 colon = buf.FindChar(':');
00105   if (colon == kNotFound)
00106   {
00107     NS_ERROR("Invalid key");
00108     return PR_FALSE;
00109   }
00110   buf.SetCharAt('\0', colon);
00111 
00112   *cid = buf.get();
00113   *key = buf.get() + colon + 1;
00114 
00115   return PR_TRUE;
00116 }
00117 
00118 class AutoResetStatement
00119 {
00120   public:
00121     AutoResetStatement(mozIStorageStatement *s)
00122       : mStatement(s) {}
00123     ~AutoResetStatement() { mStatement->Reset(); }
00124     mozIStorageStatement *operator->() { return mStatement; }
00125   private:
00126     mozIStorageStatement *mStatement;
00127 };
00128 
00129 /******************************************************************************
00130  *  nsDiskCache
00131  *****************************************************************************/
00132 
00133 #define DCACHE_HASH_MAX  LL_MAXINT
00134 #define DCACHE_HASH_BITS 64
00135 
00144 static PRUint64
00145 DCacheHash(const char * key)
00146 {
00147   PRUint64 h = 0;
00148   for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s)
00149     h = (h >> (DCACHE_HASH_BITS - 4)) ^ (h << 4) ^ *s;
00150   return (h == 0 ? DCACHE_HASH_MAX : h);
00151 }
00152 
00153 
00154 nsresult
00155 nsDiskCache::Truncate(PRFileDesc *  fd, PRUint32  newEOF)
00156 {
00157   // use modified SetEOF from nsFileStreams::SetEOF()
00158 
00159 #if defined(XP_UNIX) || defined(XP_BEOS)
00160   if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
00161     NS_ERROR("ftruncate failed");
00162     return NS_ERROR_FAILURE;
00163   }
00164 
00165 #elif defined(XP_MAC)
00166   if (::SetEOF(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
00167     NS_ERROR("SetEOF failed");
00168     return NS_ERROR_FAILURE;
00169   }
00170 
00171 #elif defined(XP_WIN)
00172   PRInt32 cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
00173   if (cnt == -1)  return NS_ERROR_FAILURE;
00174   if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
00175     NS_ERROR("SetEndOfFile failed");
00176     return NS_ERROR_FAILURE;
00177   }
00178 
00179 #elif defined(XP_OS2)
00180   if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(fd), newEOF) != NO_ERROR) {
00181     NS_ERROR("DosSetFileSize failed");
00182     return NS_ERROR_FAILURE;
00183   }
00184 #else
00185   // add implementations for other platforms here
00186 #endif
00187   return NS_OK;
00188 }
00189 
00190 
00191 /******************************************************************************
00192  * nsDiskCacheDeviceInfo
00193  */
00194 
00195 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo
00196 {
00197 public:
00198   NS_DECL_ISUPPORTS
00199   NS_DECL_NSICACHEDEVICEINFO
00200 
00201   nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
00202     : mDevice(device)
00203   {}
00204     
00205 private:
00206   nsDiskCacheDevice* mDevice;
00207 };
00208 
00209 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
00210 
00211 NS_IMETHODIMP
00212 nsDiskCacheDeviceInfo::GetDescription(char **aDescription)
00213 {
00214   *aDescription = nsCRT::strdup("Disk cache device");
00215   return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00216 }
00217 
00218 NS_IMETHODIMP
00219 nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
00220 {
00221   nsCAutoString buffer;
00222   buffer.AppendLiteral("\n<tr>\n<td><b>Cache Directory:</b></td>\n<td><tt> ");
00223   
00224   nsILocalFile *cacheDir = mDevice->CacheDirectory();
00225   if (!cacheDir)
00226     return NS_OK;
00227 
00228   nsAutoString path;
00229   nsresult rv = cacheDir->GetPath(path);
00230   if (NS_SUCCEEDED(rv))
00231     AppendUTF16toUTF8(path, buffer);
00232   else
00233     buffer.AppendLiteral("directory unavailable");
00234   buffer.AppendLiteral("</tt></td>\n</tr>\n");
00235 
00236   *usageReport = ToNewCString(buffer);
00237   if (!*usageReport)
00238     return NS_ERROR_OUT_OF_MEMORY;
00239 
00240   return NS_OK;
00241 }
00242 
00243 NS_IMETHODIMP
00244 nsDiskCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
00245 {
00246   *aEntryCount = mDevice->EntryCount();
00247   return NS_OK;
00248 }
00249 
00250 NS_IMETHODIMP
00251 nsDiskCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
00252 {
00253   *aTotalSize = mDevice->CacheSize();
00254   return NS_OK;
00255 }
00256 
00257 NS_IMETHODIMP
00258 nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
00259 {
00260   *aMaximumSize = mDevice->CacheCapacity();
00261   return NS_OK;
00262 }
00263 
00264 
00265 /******************************************************************************
00266  * nsDiskCacheBinding
00267  */
00268 
00269 class nsDiskCacheBinding : public nsISupports
00270 {
00271 public:
00272   NS_DECL_ISUPPORTS
00273 
00274   static nsDiskCacheBinding *
00275       Create(nsIFile *cacheDir, const char *key, int generation);
00276 
00277   nsCOMPtr<nsIFile> mDataFile;
00278   int               mGeneration;
00279 };
00280 
00281 NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheBinding)
00282 
00283 nsDiskCacheBinding *
00284 nsDiskCacheBinding::Create(nsIFile *cacheDir, const char *key, int generation)
00285 {
00286   nsCOMPtr<nsIFile> file;
00287   cacheDir->Clone(getter_AddRefs(file));
00288   if (!file)
00289     return nsnull;
00290 
00291   PRUint64 hash = DCacheHash(key);
00292 
00293   PRUint32 dir1 = (PRUint32) (hash & 0x0F);
00294   PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
00295 
00296   hash >>= 8;
00297 
00298   // XXX we might want to create these directories up-front
00299 
00300   file->AppendNative(nsPrintfCString("%X", dir1));
00301   file->Create(nsIFile::DIRECTORY_TYPE, 00700);
00302 
00303   file->AppendNative(nsPrintfCString("%X", dir2));
00304   file->Create(nsIFile::DIRECTORY_TYPE, 00700);
00305 
00306   nsresult rv;
00307   char leaf[64];
00308 
00309   if (generation == -1)
00310   {
00311     file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
00312 
00313     for (generation = 0; ; ++generation)
00314     {
00315       PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
00316 
00317       rv = file->SetNativeLeafName(nsDependentCString(leaf));
00318       if (NS_FAILED(rv))
00319         return nsnull;
00320       rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
00321       if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
00322         return nsnull;
00323       if (NS_SUCCEEDED(rv))
00324         break;
00325     }
00326   }
00327   else
00328   {
00329     PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
00330     rv = file->AppendNative(nsDependentCString(leaf));
00331     if (NS_FAILED(rv))
00332       return nsnull;
00333   }
00334 
00335   nsDiskCacheBinding *binding = new nsDiskCacheBinding;
00336   if (!binding)
00337     return nsnull;
00338 
00339   binding->mDataFile.swap(file);
00340   binding->mGeneration = generation;
00341   return binding;
00342 }
00343 
00344 // helper function for directly exposing the same data file binding
00345 // path algorithm used in nsDiskCacheBinding::Create
00346 static nsresult
00347 GetCacheDataFile(nsIFile *cacheDir, const char *cid, const char *key,
00348                  int generation, nsCOMPtr<nsIFile> &file)
00349 {
00350   cacheDir->Clone(getter_AddRefs(file));
00351   if (!file)
00352     return NS_ERROR_OUT_OF_MEMORY;
00353 
00354   nsCAutoString fullKey;
00355   fullKey.Append(cid);
00356   fullKey.Append(':');
00357   fullKey.Append(key);
00358 
00359   PRUint64 hash = DCacheHash(fullKey.get());
00360 
00361   PRUint32 dir1 = (PRUint32) (hash & 0x0F);
00362   PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
00363 
00364   hash >>= 8;
00365 
00366   file->AppendNative(nsPrintfCString("%X", dir1));
00367   file->AppendNative(nsPrintfCString("%X", dir2));
00368 
00369   char leaf[64];
00370   PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
00371   return file->AppendNative(nsDependentCString(leaf));
00372 }
00373 
00374 
00375 /******************************************************************************
00376  * nsDiskCacheRecord
00377  */
00378 
00379 struct nsDiskCacheRecord
00380 {
00381   const char    *clientID;
00382   const char    *key;
00383   const PRUint8 *metaData;
00384   PRUint32       metaDataLen;
00385   PRInt32        generation;
00386   PRInt32        flags;
00387   PRInt32        dataSize;
00388   PRInt32        fetchCount;
00389   PRInt64        lastFetched;
00390   PRInt64        lastModified;
00391   PRInt64        expirationTime;
00392 };
00393 
00394 static nsCacheEntry *
00395 CreateCacheEntry(nsDiskCacheDevice *device,
00396                  const nsCString *fullKey,
00397                  const nsDiskCacheRecord &rec)
00398 {
00399   if (rec.flags != 0)
00400   {
00401     LOG(("refusing to load busy entry\n"));
00402     return nsnull;
00403   }
00404 
00405   nsCacheEntry *entry;
00406   
00407   nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
00408                                      nsICache::STREAM_BASED,
00409                                      nsICache::STORE_ON_DISK,
00410                                      device, &entry);
00411   if (NS_FAILED(rv))
00412     return nsnull;
00413 
00414   entry->SetFetchCount((PRUint32) rec.fetchCount);
00415   entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
00416   entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
00417   entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
00418   entry->SetDataSize((PRUint32) rec.dataSize);
00419 
00420   entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
00421 
00422   // create a binding object for this entry
00423   nsDiskCacheBinding *binding =
00424       nsDiskCacheBinding::Create(device->CacheDirectory(),
00425                                  fullKey->get(),
00426                                  rec.generation);
00427   if (!binding)
00428   {
00429     delete entry;
00430     return nsnull;
00431   }
00432   entry->SetData(binding);
00433 
00434   return entry;
00435 }
00436 
00437 
00438 /******************************************************************************
00439  * nsDiskCacheEntryInfo
00440  */
00441 
00442 class nsDiskCacheEntryInfo : public nsICacheEntryInfo
00443 {
00444 public:
00445   NS_DECL_ISUPPORTS
00446   NS_DECL_NSICACHEENTRYINFO
00447 
00448   nsDiskCacheRecord *mRec;
00449 };
00450 
00451 NS_IMPL_ISUPPORTS1(nsDiskCacheEntryInfo, nsICacheEntryInfo)
00452 
00453 NS_IMETHODIMP
00454 nsDiskCacheEntryInfo::GetClientID(char **result)
00455 {
00456   *result = nsCRT::strdup(mRec->clientID);
00457   return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00458 }
00459 
00460 NS_IMETHODIMP
00461 nsDiskCacheEntryInfo::GetDeviceID(char ** deviceID)
00462 {
00463   *deviceID = nsCRT::strdup(DISK_CACHE_DEVICE_ID);
00464   return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00465 }
00466 
00467 NS_IMETHODIMP
00468 nsDiskCacheEntryInfo::GetKey(nsACString &clientKey)
00469 {
00470   clientKey.Assign(mRec->key);
00471   return NS_OK;
00472 }
00473 
00474 NS_IMETHODIMP
00475 nsDiskCacheEntryInfo::GetFetchCount(PRInt32 *aFetchCount)
00476 {
00477   *aFetchCount = mRec->fetchCount;
00478   return NS_OK;
00479 }
00480 
00481 NS_IMETHODIMP
00482 nsDiskCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched)
00483 {
00484   *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
00485   return NS_OK;
00486 }
00487 
00488 NS_IMETHODIMP
00489 nsDiskCacheEntryInfo::GetLastModified(PRUint32 *aLastModified)
00490 {
00491   *aLastModified = SecondsFromPRTime(mRec->lastModified);
00492   return NS_OK;
00493 }
00494 
00495 NS_IMETHODIMP
00496 nsDiskCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime)
00497 {
00498   *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
00499   return NS_OK;
00500 }
00501 
00502 NS_IMETHODIMP
00503 nsDiskCacheEntryInfo::IsStreamBased(PRBool *aStreamBased)
00504 {
00505   *aStreamBased = PR_TRUE;
00506   return NS_OK;
00507 }
00508 
00509 NS_IMETHODIMP nsDiskCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
00510 {
00511   *aDataSize = mRec->dataSize;
00512   return NS_OK;
00513 }
00514 
00515 
00516 /******************************************************************************
00517  * nsDiskCacheDevice
00518  */
00519 
00520 nsDiskCacheDevice::nsDiskCacheDevice()
00521   : mDB(nsnull)
00522   , mCacheCapacity(0)
00523   , mDeltaCounter(0)
00524 {
00525 }
00526 
00527 nsDiskCacheDevice::~nsDiskCacheDevice()
00528 {
00529   Shutdown();
00530 }
00531 
00532 PRUint32
00533 nsDiskCacheDevice::CacheSize()
00534 {
00535   AutoResetStatement statement(mStatement_CacheSize);
00536 
00537   PRBool hasRows;
00538   nsresult rv = statement->ExecuteStep(&hasRows);
00539   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
00540   
00541   return (PRUint32) statement->AsInt32(0);
00542 }
00543 
00544 PRUint32
00545 nsDiskCacheDevice::EntryCount()
00546 {
00547   AutoResetStatement statement(mStatement_EntryCount);
00548 
00549   PRBool hasRows;
00550   nsresult rv = statement->ExecuteStep(&hasRows);
00551   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
00552 
00553   return (PRUint32) statement->AsInt32(0);
00554 }
00555 
00556 nsresult
00557 nsDiskCacheDevice::UpdateEntry(nsCacheEntry *entry)
00558 {
00559   // Decompose the key into "ClientID" and "Key"
00560   nsCAutoString keyBuf;
00561   const char *cid, *key;
00562   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
00563     return NS_ERROR_UNEXPECTED;
00564 
00565   nsCString metaDataBuf;
00566   PRUint32 mdSize = entry->MetaDataSize();
00567   if (!EnsureStringLength(metaDataBuf, mdSize))
00568     return NS_ERROR_OUT_OF_MEMORY;
00569   char *md = metaDataBuf.BeginWriting();
00570   entry->FlattenMetaData(md, mdSize);
00571 
00572   nsDiskCacheRecord rec;
00573   rec.metaData = (const PRUint8 *) md;
00574   rec.metaDataLen = mdSize;
00575   rec.flags = 0;  // mark entry as inactive
00576   rec.dataSize = entry->DataSize();
00577   rec.fetchCount = entry->FetchCount();
00578   rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
00579   rec.lastModified = PRTimeFromSeconds(entry->LastModified());
00580   rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
00581 
00582   AutoResetStatement statement(mStatement_UpdateEntry);
00583 
00584   nsresult rv;
00585   rv  = statement->BindDataParameter(0, rec.metaData, rec.metaDataLen);
00586   rv |= statement->BindInt32Parameter(1, rec.flags);
00587   rv |= statement->BindInt32Parameter(2, rec.dataSize);
00588   rv |= statement->BindInt32Parameter(3, rec.fetchCount);
00589   rv |= statement->BindInt64Parameter(4, rec.lastFetched);
00590   rv |= statement->BindInt64Parameter(5, rec.lastModified);
00591   rv |= statement->BindInt64Parameter(6, rec.expirationTime);
00592   rv |= statement->BindCStringParameter(7, cid);
00593   rv |= statement->BindCStringParameter(8, key);
00594   NS_ENSURE_SUCCESS(rv, rv);
00595 
00596   PRBool hasRows;
00597   rv = statement->ExecuteStep(&hasRows);
00598   NS_ENSURE_SUCCESS(rv, rv);
00599 
00600   NS_ASSERTION(!hasRows, "UPDATE should not result in output");
00601   return rv;
00602 }
00603 
00604 nsresult
00605 nsDiskCacheDevice::UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize)
00606 {
00607   // Decompose the key into "ClientID" and "Key"
00608   nsCAutoString keyBuf;
00609   const char *cid, *key;
00610   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
00611     return NS_ERROR_UNEXPECTED;
00612 
00613   AutoResetStatement statement(mStatement_UpdateEntrySize);
00614 
00615   nsresult rv;
00616   rv  = statement->BindInt32Parameter(0, newSize);
00617   rv |= statement->BindCStringParameter(1, cid);
00618   rv |= statement->BindCStringParameter(2, key);
00619   NS_ENSURE_SUCCESS(rv, rv);
00620 
00621   PRBool hasRows;
00622   rv = statement->ExecuteStep(&hasRows);
00623   NS_ENSURE_SUCCESS(rv, rv);
00624 
00625   NS_ASSERTION(!hasRows, "UPDATE should not result in output");
00626   return rv;
00627 }
00628 
00629 nsresult
00630 nsDiskCacheDevice::DeleteEntry(nsCacheEntry *entry, PRBool deleteData)
00631 {
00632   if (deleteData)
00633   {
00634     nsresult rv = DeleteData(entry);
00635     if (NS_FAILED(rv))
00636       return rv;
00637   }
00638 
00639   // Decompose the key into "ClientID" and "Key"
00640   nsCAutoString keyBuf;
00641   const char *cid, *key;
00642   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
00643     return NS_ERROR_UNEXPECTED;
00644 
00645   AutoResetStatement statement(mStatement_DeleteEntry);
00646 
00647   nsresult rv;
00648   rv  = statement->BindCStringParameter(0, cid);
00649   rv |= statement->BindCStringParameter(1, key);
00650   NS_ENSURE_SUCCESS(rv, rv);
00651 
00652   PRBool hasRows;
00653   rv = statement->ExecuteStep(&hasRows);
00654   NS_ENSURE_SUCCESS(rv, rv);
00655 
00656   NS_ASSERTION(!hasRows, "DELETE should not result in output");
00657   return rv;
00658 }
00659 
00660 nsresult
00661 nsDiskCacheDevice::DeleteData(nsCacheEntry *entry)
00662 {
00663   nsDiskCacheBinding *binding = (nsDiskCacheBinding *) entry->Data();
00664   NS_ENSURE_STATE(binding);
00665 
00666   return binding->mDataFile->Remove(PR_FALSE);
00667 }
00668 
00669 nsresult
00670 nsDiskCacheDevice::EnableEvictionObserver()
00671 {
00672 #if 0
00673   // use CreateTrigger .. maybe do this only once, and have a member
00674   // variable to control whether or not it is active.
00675 
00676   int res =
00677       sqlite3_exec(mDB, "CREATE TEMP TRIGGER cache_on_delete AFTER DELETE"
00678                         " ON moz_cache FOR EACH ROW BEGIN SELECT"
00679                         " cache_eviction_observer("
00680                         "  OLD.clientID, OLD.key, OLD.generation);"
00681                         " END;", NULL, NULL, NULL);
00682   NS_ENSURE_SQLITE_RESULT(res, NS_ERROR_UNEXPECTED);
00683 #endif
00684 
00685   return NS_OK;
00686 }
00687 
00688 nsresult
00689 nsDiskCacheDevice::DisableEvictionObserver()
00690 {
00691 #if 0
00692   int res = sqlite3_exec(mDB, "DROP TRIGGER cache_on_delete;",
00693                          NULL, NULL, NULL);
00694   NS_ENSURE_SQLITE_RESULT(res, NS_ERROR_UNEXPECTED);
00695 #endif
00696 
00697   return NS_OK;
00698 }
00699 
00700 #if 0
00701 /* static */ void
00702 nsDiskCacheDevice::EvictionObserver(sqlite3_context *ctx, int narg,
00703                                     sqlite3_value **values)
00704 {
00705   LOG(("nsDiskCacheDevice::EvictionObserver\n"));
00706 
00707   nsDiskCacheDevice *device = (nsDiskCacheDevice *) sqlite3_user_data(ctx);
00708 
00709   NS_ASSERTION(narg == 3, "unexpected number of arguments");
00710   const char *cid = (const char *) sqlite3_value_text(values[0]);
00711   const char *key = (const char *) sqlite3_value_text(values[1]);
00712   int generation  =                sqlite3_value_int(values[2]);
00713 
00714   nsCOMPtr<nsIFile> file;
00715   nsresult rv = GetCacheDataFile(device->CacheDirectory(), cid, key,
00716                                  generation, file);
00717   if (NS_FAILED(rv))
00718   {
00719     LOG(("GetCacheDataFile [cid=%s key=%s generation=%d] failed [rv=%x]!\n",
00720         cid, key, generation, rv));
00721     return;
00722   }
00723 
00724 #if defined(PR_LOGGING)
00725   nsCAutoString path;
00726   file->GetNativePath(path);
00727   LOG(("  removing %s\n", path.get()));
00728 #endif
00729 
00730   file->Remove(PR_FALSE);
00731 }
00732 #endif
00733 
00734 
00739 nsresult
00740 nsDiskCacheDevice::Init()
00741 {
00742   NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
00743 
00744   // SetCacheParentDirectory must have been called
00745   NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
00746 
00747   // make sure the cache directory exists
00748   nsresult rv = EnsureDir(mCacheDirectory);
00749   NS_ENSURE_SUCCESS(rv, rv);
00750 
00751   // build path to index file
00752   nsCOMPtr<nsIFile> indexFile; 
00753   rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
00754   NS_ENSURE_SUCCESS(rv, rv);
00755   rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.db"));
00756   NS_ENSURE_SUCCESS(rv, rv);
00757 
00758   nsCOMPtr<mozIStorageService> ss =
00759       do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
00760   NS_ENSURE_SUCCESS(rv, rv);
00761 
00762   rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
00763   NS_ENSURE_SUCCESS(rv, rv);
00764 
00765   mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
00766 
00767   // XXX ... other initialization steps
00768 
00769   // XXX in the future we may wish to verify the schema for moz_cache
00770   //     perhaps using "PRAGMA table_info" ?
00771 
00772   // build the table
00773   //
00774   //  "Generation" is the data file generation number.
00775   //  "Flags" is a bit-field indicating the state of the entry.
00776   //
00777   mDB->ExecuteSimpleSQL(
00778       NS_LITERAL_CSTRING("CREATE TABLE moz_cache (\n"
00779                          "  ClientID        TEXT,\n"
00780                          "  Key             TEXT,\n"
00781                          "  MetaData        BLOB,\n"
00782                          "  Generation      INTEGER,\n"
00783                          "  Flags           INTEGER,\n"
00784                          "  DataSize        INTEGER,\n"
00785                          "  FetchCount      INTEGER,\n"
00786                          "  LastFetched     INTEGER,\n"
00787                          "  LastModified    INTEGER,\n"
00788                          "  ExpirationTime  INTEGER\n"
00789                          ");\n"));
00790   // maybe the table already exists, so don't bother checking for errors.
00791 
00792   mDB->ExecuteSimpleSQL(
00793       NS_LITERAL_CSTRING("CREATE UNIQUE INDEX moz_cache_index"
00794                          " ON moz_cache (ClientID, Key);"));
00795   // maybe the index already exists, so don't bother checking for errors.
00796 
00797 #if 0
00798   // create cache_eviction_observer
00799   res = sqlite3_create_function(mDB, "cache_eviction_observer",
00800                                 3, SQLITE_UTF8, this,
00801                                 nsDiskCacheDevice::EvictionObserver,
00802                                 NULL, NULL);
00803   if (res != SQLITE_OK)
00804     LOG(("sqlite3_create_function failed [res=%d]\n", res));
00805 #endif
00806 
00807   // create all (most) of our statements up front
00808   struct {
00809     nsCOMPtr<mozIStorageStatement> &statement;
00810     const char *sql;
00811   } prepared[] = {
00812     { mStatement_CacheSize,       "SELECT Sum(DataSize) from moz_cache;" },
00813     { mStatement_EntryCount,      "SELECT count(*) from moz_cache;" },
00814     { mStatement_UpdateEntry,     "UPDATE moz_cache SET MetaData = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" },
00815     { mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" },
00816     { mStatement_DeleteEntry,     "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" },
00817     { mStatement_FindEntry,       "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime FROM moz_cache WHERE ClientID = ? AND Key = ?;" },
00818     { mStatement_BindEntry,       "INSERT INTO moz_cache VALUES(?,?,?,?,?,?,?,?,?,?);" }
00819   };
00820   for (PRUint32 i=0; i<NS_ARRAY_LENGTH(prepared); ++i)
00821   {
00822     rv |= mDB->CreateStatement(nsDependentCString(prepared[i].sql),
00823                                getter_AddRefs(prepared[i].statement));
00824   }
00825   NS_ENSURE_SUCCESS(rv, rv);
00826 
00827   return NS_OK;
00828 }
00829 
00830 nsresult
00831 nsDiskCacheDevice::Shutdown()
00832 {
00833   NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
00834 
00835 #if 0
00836   int res;
00837 
00838   // delete cache_eviction_observer
00839   res = sqlite3_create_function(mDB, "cache_eviction_observer", 3, SQLITE_UTF8,
00840                                 NULL, NULL, NULL, NULL);
00841   if (res != SQLITE_OK)
00842     LOG(("sqlite3_create_function failed [res=%d]\n", res));
00843   
00844   res = sqlite3_close(mDB);
00845   NS_ENSURE_SQLITE_RESULT(res, NS_ERROR_UNEXPECTED);
00846 
00847   mDB = nsnull;
00848 #endif
00849 
00850   mDB = 0;
00851   return NS_OK;
00852 }
00853 
00854 const char *
00855 nsDiskCacheDevice::GetDeviceID()
00856 {
00857   return DISK_CACHE_DEVICE_ID;
00858 }
00859 
00860 nsCacheEntry *
00861 nsDiskCacheDevice::FindEntry(nsCString *fullKey, PRBool *collision)
00862 {
00863   LOG(("nsDiskCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
00864 
00865   // SELECT * FROM moz_cache WHERE key = ?
00866 
00867   // Decompose the key into "ClientID" and "Key"
00868   nsCAutoString keyBuf;
00869   const char *cid, *key;
00870   if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
00871     return nsnull;
00872 
00873   AutoResetStatement statement(mStatement_FindEntry);
00874 
00875   nsresult rv;
00876   rv  = statement->BindCStringParameter(0, cid);
00877   rv |= statement->BindCStringParameter(1, key);
00878   NS_ENSURE_SUCCESS(rv, nsnull);
00879 
00880   PRBool hasRows;
00881   rv = statement->ExecuteStep(&hasRows);
00882   if (NS_FAILED(rv) || !hasRows)
00883     return nsnull; // entry not found
00884 
00885   nsDiskCacheRecord rec;
00886   statement->AsSharedBlob(0, (const void **) &rec.metaData, &rec.metaDataLen);
00887   rec.generation     = statement->AsInt32(1);
00888   rec.flags          = statement->AsInt32(2);
00889   rec.dataSize       = statement->AsInt32(3);
00890   rec.fetchCount     = statement->AsInt32(4);
00891   rec.lastFetched    = statement->AsInt64(5);
00892   rec.lastModified   = statement->AsInt64(6);
00893   rec.expirationTime = statement->AsInt64(7);
00894 
00895   LOG(("entry: [%u %d %d %d %d %lld %lld %lld]\n",
00896         rec.metaDataLen,
00897         rec.generation,
00898         rec.flags,
00899         rec.dataSize,
00900         rec.fetchCount,
00901         rec.lastFetched,
00902         rec.lastModified,
00903         rec.expirationTime));
00904 
00905   return CreateCacheEntry(this, fullKey, rec);
00906 
00907   // XXX we should verify that the corresponding data file exists,
00908   //     and if not, then we should delete this row and return null.
00909 }
00910 
00911 nsresult
00912 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry *entry)
00913 {
00914   LOG(("nsDiskCacheDevice::DeactivateEntry [key=%s]\n", entry->Key()->get()));
00915 
00916   // This method is called to inform us that the nsCacheEntry object is going
00917   // away.  We should persist anything that needs to be persisted, or if the
00918   // entry is doomed, we can go ahead and clear its storage.
00919 
00920   if (entry->IsDoomed())
00921   {
00922     // remove corresponding row and file if they exist
00923 
00924     // the row should have been removed in DoomEntry... we could assert that
00925     // that happened.  otherwise, all we have to do here is delete the file
00926     // on disk.
00927     DeleteData(entry);
00928   }
00929   else
00930   {
00931     // UPDATE the database row
00932 
00933     // XXX Assumption: the row already exists because it was either created
00934     // with a call to BindEntry or it was there when we called FindEntry.
00935 
00936     UpdateEntry(entry);
00937   }
00938 
00939   delete entry;
00940   return NS_OK;
00941 }
00942 
00943 nsresult
00944 nsDiskCacheDevice::BindEntry(nsCacheEntry *entry)
00945 {
00946   LOG(("nsDiskCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
00947 
00948   NS_ENSURE_STATE(!entry->Data());
00949 
00950   // This method is called to inform us that we have a new entry.  The entry
00951   // may collide with an existing entry in our DB, but if that happens we can
00952   // assume that the entry is not being used.
00953 
00954   // INSERT the database row
00955 
00956   // XXX Assumption: if the row already exists, then FindEntry would have
00957   // returned it.  if that entry was doomed, then DoomEntry would have removed
00958   // it from the table.  so, we should always have to insert at this point.
00959 
00960   // Decompose the key into "ClientID" and "Key"
00961   nsCAutoString keyBuf;
00962   const char *cid, *key;
00963   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
00964     return NS_ERROR_UNEXPECTED;
00965 
00966   // create binding, pick best generation number
00967   nsRefPtr<nsDiskCacheBinding> binding =
00968       nsDiskCacheBinding::Create(mCacheDirectory, entry->Key()->get(), -1);
00969   if (!binding)
00970     return NS_ERROR_OUT_OF_MEMORY;
00971 
00972   nsDiskCacheRecord rec;
00973   rec.clientID = cid;
00974   rec.key = key;
00975   rec.metaData = NULL; // don't write any metadata now.
00976   rec.metaDataLen = 0;
00977   rec.generation = binding->mGeneration;
00978   rec.flags = 0x1;  // mark entry as active, we'll reset this in DeactivateEntry
00979   rec.dataSize = 0;
00980   rec.fetchCount = entry->FetchCount();
00981   rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
00982   rec.lastModified = PRTimeFromSeconds(entry->LastModified());
00983   rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
00984 
00985   AutoResetStatement statement(mStatement_BindEntry);
00986 
00987   nsresult rv;
00988   rv  = statement->BindCStringParameter(0, rec.clientID);
00989   rv |= statement->BindCStringParameter(1, rec.key);
00990   rv |= statement->BindDataParameter(2, rec.metaData, rec.metaDataLen);
00991   rv |= statement->BindInt32Parameter(3, rec.generation);
00992   rv |= statement->BindInt32Parameter(4, rec.flags);
00993   rv |= statement->BindInt32Parameter(5, rec.dataSize);
00994   rv |= statement->BindInt32Parameter(6, rec.fetchCount);
00995   rv |= statement->BindInt64Parameter(7, rec.lastFetched);
00996   rv |= statement->BindInt64Parameter(8, rec.lastModified);
00997   rv |= statement->BindInt64Parameter(9, rec.expirationTime);
00998   NS_ENSURE_SUCCESS(rv, rv);
00999   
01000   PRBool hasRows;
01001   rv = statement->ExecuteStep(&hasRows);
01002   NS_ENSURE_SUCCESS(rv, rv);
01003 
01004   NS_ASSERTION(!hasRows, "INSERT should not result in output");
01005 
01006   entry->SetData(binding);
01007   return NS_OK;
01008 }
01009 
01010 void
01011 nsDiskCacheDevice::DoomEntry(nsCacheEntry *entry)
01012 {
01013   LOG(("nsDiskCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
01014 
01015   // This method is called to inform us that we should mark the entry to be
01016   // deleted when it is no longer in use.
01017 
01018   // We can go ahead and delete the corresponding row in our table,
01019   // but we must not delete the file on disk until we are deactivated.
01020   
01021   DeleteEntry(entry, PR_FALSE);
01022 }
01023 
01024 nsresult
01025 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry      *entry,
01026                                            nsCacheAccessMode  mode,
01027                                            PRUint32           offset,
01028                                            nsIInputStream   **result)
01029 {
01030   LOG(("nsDiskCacheDevice::OpenInputStreamForEntry [key=%s]\n", entry->Key()->get()));
01031 
01032   *result = nsnull;
01033 
01034   NS_ENSURE_TRUE(offset < entry->DataSize(), NS_ERROR_INVALID_ARG);
01035 
01036   // return an input stream to the entry's data file.  the stream
01037   // may be read on a background thread.
01038 
01039   nsDiskCacheBinding *binding = (nsDiskCacheBinding *) entry->Data();
01040   NS_ENSURE_STATE(binding);
01041 
01042   nsCOMPtr<nsIInputStream> in;
01043   NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
01044   if (!in)
01045     return NS_ERROR_UNEXPECTED;
01046 
01047   // respect |offset| param
01048   if (offset != 0)
01049   {
01050     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
01051     NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
01052 
01053     seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
01054   }
01055 
01056   in.swap(*result);
01057   return NS_OK;
01058 }
01059 
01060 nsresult
01061 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry       *entry,
01062                                             nsCacheAccessMode   mode,
01063                                             PRUint32            offset,
01064                                             nsIOutputStream   **result)
01065 {
01066   LOG(("nsDiskCacheDevice::OpenOutputStreamForEntry [key=%s]\n", entry->Key()->get()));
01067 
01068   *result = nsnull;
01069 
01070   NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
01071 
01072   // return an output stream to the entry's data file.  we can assume
01073   // that the output stream will only be used on the main thread.
01074 
01075   nsDiskCacheBinding *binding = (nsDiskCacheBinding *) entry->Data();
01076   NS_ENSURE_STATE(binding);
01077 
01078   nsCOMPtr<nsIOutputStream> out;
01079   NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
01080                               PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
01081                               00600);
01082   if (!out)
01083     return NS_ERROR_UNEXPECTED;
01084 
01085   // respect |offset| param
01086   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
01087   NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
01088   if (offset != 0)
01089     seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
01090 
01091   // truncate the file at the given offset
01092   seekable->SetEOF();
01093 
01094   nsCOMPtr<nsIOutputStream> bufferedOut;
01095   NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
01096   if (!bufferedOut)
01097     return NS_ERROR_UNEXPECTED;
01098 
01099   bufferedOut.swap(*result);
01100   return NS_OK;
01101 }
01102 
01103 nsresult
01104 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
01105 {
01106   LOG(("nsDiskCacheDevice::GetFileForEntry [key=%s]\n", entry->Key()->get()));
01107 
01108   nsDiskCacheBinding *binding = (nsDiskCacheBinding *) entry->Data();
01109   NS_ENSURE_STATE(binding);
01110 
01111   NS_IF_ADDREF(*result = binding->mDataFile);
01112   return NS_OK;
01113 }
01114 
01115 nsresult
01116 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry *entry, PRInt32 deltaSize)
01117 {
01118   LOG(("nsDiskCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
01119       entry->Key()->get(), deltaSize));
01120 
01121   // called to notify us of an impending change in the total size of the
01122   // specified entry.  we may wish to trigger an eviction cycle at this point.
01123 
01124   // update the DataSize attribute for this entry
01125   /*
01126   PRUint32 oldSize = entry->DataSize();
01127   NS_ASSERTION(deltaSize >= 0 || PRInt32(oldSize) + deltaSize >= 0, "oops");
01128   PRUint32 newSize = PRInt32(oldSize) + deltaSize;
01129   UpdateEntrySize(entry, newSize);
01130   */
01131 
01132   const PRInt32 DELTA_THRESHOLD = 1<<14; // 16k
01133 
01134   mDeltaCounter += deltaSize; // this may go negative
01135 
01136   // run eviction cycle
01137   if (mDeltaCounter >= DELTA_THRESHOLD)
01138   {
01139     PRUint32 targetCap, delta = mDeltaCounter;
01140     if (delta <= mCacheCapacity)
01141       targetCap = mCacheCapacity - delta;
01142     else
01143       targetCap = 0;
01144     EvictDiskCacheEntries(targetCap);
01145     mDeltaCounter = 0; // reset counter
01146   }
01147 
01148   return NS_OK;
01149 }
01150 
01151 nsresult
01152 nsDiskCacheDevice::Visit(nsICacheVisitor *visitor)
01153 {
01154   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
01155 
01156   // called to enumerate the disk cache.
01157 
01158   nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
01159       new nsDiskCacheDeviceInfo(this);
01160 
01161   PRBool keepGoing;
01162   nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo,
01163                                      &keepGoing);
01164   if (NS_FAILED(rv))
01165     return rv;
01166   
01167   if (!keepGoing)
01168     return NS_OK;
01169 
01170   // SELECT * from moz_cache;
01171 
01172   nsDiskCacheRecord rec;
01173   nsRefPtr<nsDiskCacheEntryInfo> info = new nsDiskCacheEntryInfo;
01174   if (!info)
01175     return NS_ERROR_OUT_OF_MEMORY;
01176   info->mRec = &rec;
01177 
01178   // XXX may want to list columns explicitly
01179   nsCOMPtr<mozIStorageStatement> statement;
01180   rv = mDB->CreateStatement(
01181       NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
01182       getter_AddRefs(statement));
01183   NS_ENSURE_SUCCESS(rv, rv);
01184 
01185   PRBool hasRows;
01186   for (;;)
01187   {
01188     rv = statement->ExecuteStep(&hasRows);
01189     if (NS_FAILED(rv) || !hasRows)
01190       break;
01191 
01192     rec.clientID       = statement->AsSharedCString(0, NULL);
01193     rec.key            = statement->AsSharedCString(1, NULL);
01194     statement->AsSharedBlob(2, (const void **) &rec.metaData, &rec.metaDataLen);
01195     rec.generation     = statement->AsInt32(3);
01196     rec.flags          = statement->AsInt32(4);
01197     rec.dataSize       = statement->AsInt32(5);
01198     rec.fetchCount     = statement->AsInt32(6);
01199     rec.lastFetched    = statement->AsInt64(7);
01200     rec.lastModified   = statement->AsInt64(8);
01201     rec.expirationTime = statement->AsInt64(9);
01202 
01203     PRBool keepGoing;
01204     rv = visitor->VisitEntry(DISK_CACHE_DEVICE_ID, info, &keepGoing);
01205     if (NS_FAILED(rv) || !keepGoing)
01206       break;
01207   }
01208 
01209   info->mRec = nsnull;
01210   return NS_OK;
01211 }
01212 
01213 nsresult
01214 nsDiskCacheDevice::EvictEntries(const char *clientID)
01215 {
01216   LOG(("nsDiskCacheDevice::EvictEntries [cid=%s]\n", clientID ? clientID : ""));
01217 
01218   // called to evict all entries matching the given clientID.
01219 
01220   // need trigger to fire user defined function after a row is deleted
01221   // so we can delete the corresponding data file.
01222 
01223   nsresult rv = EnableEvictionObserver();
01224   NS_ENSURE_SUCCESS(rv, rv);
01225 
01226   // hook up our eviction observer
01227   
01228   const char *deleteCmd;
01229   if (clientID)
01230   {
01231     deleteCmd =
01232       PR_smprintf("DELETE FROM moz_cache WHERE ClientID=%q AND Flags=0;",
01233                   clientID);
01234     if (!deleteCmd)
01235       return NS_ERROR_OUT_OF_MEMORY;
01236   }
01237   else
01238   {
01239     deleteCmd = "DELETE FROM moz_cache WHERE Flags = 0;";
01240   }
01241 
01242   rv = mDB->ExecuteSimpleSQL(nsDependentCString(deleteCmd));
01243   if (clientID)
01244     PR_smprintf_free((char *) deleteCmd);
01245   NS_ENSURE_SUCCESS(rv, rv);
01246 
01247   DisableEvictionObserver();
01248 
01249   return NS_OK;
01250 }
01251 
01252 nsresult
01253 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 desiredCapacity)
01254 {
01255   LOG(("nsDiskCacheDevice::EvictDiskCacheEntries [goal=%u delta=%d]\n",
01256       desiredCapacity, mCacheCapacity - desiredCapacity));
01257 
01258   // need trigger to fire user defined function after a row is deleted
01259   // so we can delete the corresponding data file.
01260 
01261   // BEGIN
01262   // while ("SELECT Sum(DataSize) FROM moz_cache;" > desiredCapacity)
01263   //   DELETE FROM moz_cache WHERE Min(LastFetched);
01264   // END
01265 
01266   nsresult rv = EnableEvictionObserver();
01267   NS_ENSURE_SUCCESS(rv, rv);
01268 
01269   PRUint32 lastCacheSize = PR_UINT32_MAX, cacheSize;
01270   for (;;)
01271   {
01272     cacheSize = CacheSize();
01273     if (cacheSize <= desiredCapacity)
01274       break;
01275     if (cacheSize == lastCacheSize)
01276     {
01277       LOG(("unable to reduce cache size to target capacity!\n"));
01278       break;
01279     }
01280 
01281     rv = mDB->ExecuteSimpleSQL(
01282         NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE LastFetched IN ("
01283                            " SELECT Min(LastFetched) FROM moz_cache"
01284                            " WHERE Flags=0);"));
01285     if (NS_FAILED(rv))
01286     {
01287       LOG(("failure while deleting Min(LastFetched)\n"));
01288       break;
01289     }
01290 
01291     lastCacheSize = cacheSize;
01292   }
01293 
01294   DisableEvictionObserver();
01295   return NS_OK;
01296 }
01297 
01298 
01303 void
01304 nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile *parentDir)
01305 {
01306   if (Initialized())
01307   {
01308     NS_ERROR("cannot switch cache directory once initialized");
01309     return;
01310   }
01311 
01312   if (!parentDir)
01313   {
01314     mCacheDirectory = nsnull;
01315     return;
01316   }
01317 
01318   // ensure parent directory exists
01319   nsresult rv = EnsureDir(parentDir);
01320   if (NS_FAILED(rv))
01321   {
01322     NS_WARNING("unable to create parent directory");
01323     return;
01324   }
01325 
01326   // cache dir may not exist, but that's ok
01327   nsCOMPtr<nsIFile> dir;
01328   rv = parentDir->Clone(getter_AddRefs(dir));
01329   if (NS_FAILED(rv))
01330     return;
01331   rv = dir->AppendNative(NS_LITERAL_CSTRING("cache_sql"));
01332   if (NS_FAILED(rv))
01333     return;
01334 
01335   mCacheDirectory = do_QueryInterface(dir);
01336 }
01337 
01338 void
01339 nsDiskCacheDevice::SetCapacity(PRUint32 capacity)
01340 {
01341   mCacheCapacity = capacity * 1024;
01342   if (Initialized())
01343     EvictDiskCacheEntries(mCacheCapacity);
01344 }