Bug 597224 - HTTP Cache: use directory tree to store cache files. sr=bzbarsky@mit...
[mozilla-central.git] / netwerk / cache / nsDiskCacheDevice.cpp
blob332fb5db7400bf292a44c16a3e5a0637d85c3741
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is nsDiskCacheDevice.cpp, released
17 * February 22, 2001.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Gordon Sheridan <gordon@netscape.com>
26 * Patrick C. Beard <beard@netscape.com>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include <limits.h>
44 // include files for ftruncate (or equivalent)
45 #if defined(XP_UNIX) || defined(XP_BEOS)
46 #include <unistd.h>
47 #elif defined(XP_WIN)
48 #include <windows.h>
49 #elif defined(XP_OS2)
50 #define INCL_DOSERRORS
51 #include <os2.h>
52 #else
53 // XXX add necessary include file for ftruncate (or equivalent)
54 #endif
56 #include "prtypes.h"
57 #include "prthread.h"
58 #include "prbit.h"
60 #include "private/pprio.h"
62 #include "nsDiskCacheDevice.h"
63 #include "nsDiskCacheEntry.h"
64 #include "nsDiskCacheMap.h"
65 #include "nsDiskCacheStreams.h"
67 #include "nsDiskCache.h"
69 #include "nsCacheService.h"
70 #include "nsCache.h"
72 #include "nsDeleteDir.h"
74 #include "nsICacheVisitor.h"
75 #include "nsReadableUtils.h"
76 #include "nsIInputStream.h"
77 #include "nsIOutputStream.h"
78 #include "nsAutoLock.h"
79 #include "nsCRT.h"
80 #include "nsCOMArray.h"
81 #include "nsISimpleEnumerator.h"
83 #include "mozilla/FunctionTimer.h"
85 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
88 /******************************************************************************
89 * nsDiskCacheEvictor
91 * Helper class for nsDiskCacheDevice.
93 *****************************************************************************/
95 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
97 public:
98 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
99 nsDiskCacheBindery * cacheBindery,
100 PRUint32 targetSize,
101 const char * clientID)
102 : mCacheMap(cacheMap)
103 , mBindery(cacheBindery)
104 , mTargetSize(targetSize)
105 , mClientID(clientID)
107 mClientIDSize = clientID ? strlen(clientID) : 0;
110 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord);
112 private:
113 nsDiskCacheMap * mCacheMap;
114 nsDiskCacheBindery * mBindery;
115 PRUint32 mTargetSize;
116 const char * mClientID;
117 PRUint32 mClientIDSize;
121 PRInt32
122 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
124 if (mCacheMap->TotalSize() < mTargetSize)
125 return kStopVisitingRecords;
127 if (mClientID) {
128 // we're just evicting records for a specific client
129 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
130 if (!diskEntry)
131 return kVisitNextRecord; // XXX or delete record?
133 // Compare clientID's without malloc
134 if ((diskEntry->mKeySize <= mClientIDSize) ||
135 (diskEntry->Key()[mClientIDSize] != ':') ||
136 (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
137 return kVisitNextRecord; // clientID doesn't match, skip it
141 nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
142 if (binding) {
143 // We are currently using this entry, so all we can do is doom it.
144 // Since we're enumerating the records, we don't want to call
145 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
146 binding->mDoomed = PR_TRUE; // mark binding record as 'deleted'
147 nsCacheService::DoomEntry(binding->mCacheEntry);
148 } else {
149 // entry not in use, just delete storage because we're enumerating the records
150 (void) mCacheMap->DeleteStorage(mapRecord);
153 return kDeleteRecordAndContinue; // this will REALLY delete the record
157 /******************************************************************************
158 * nsDiskCacheDeviceInfo
159 *****************************************************************************/
161 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
162 public:
163 NS_DECL_ISUPPORTS
164 NS_DECL_NSICACHEDEVICEINFO
166 nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
167 : mDevice(device)
171 virtual ~nsDiskCacheDeviceInfo() {}
173 private:
174 nsDiskCacheDevice* mDevice;
177 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
179 /* readonly attribute string description; */
180 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
182 NS_ENSURE_ARG_POINTER(aDescription);
183 *aDescription = NS_strdup("Disk cache device");
184 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
187 /* readonly attribute string usageReport; */
188 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
190 NS_ENSURE_ARG_POINTER(usageReport);
191 nsCString buffer;
193 buffer.AssignLiteral(" <tr>\n"
194 " <th>Cache Directory:</th>\n"
195 " <td>");
196 nsCOMPtr<nsILocalFile> cacheDir;
197 nsAutoString path;
198 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
199 nsresult rv = cacheDir->GetPath(path);
200 if (NS_SUCCEEDED(rv)) {
201 AppendUTF16toUTF8(path, buffer);
202 } else {
203 buffer.AppendLiteral("directory unavailable");
205 buffer.AppendLiteral("</td>\n"
206 " </tr>\n");
208 *usageReport = ToNewCString(buffer);
209 if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
211 return NS_OK;
214 /* readonly attribute unsigned long entryCount; */
215 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
217 NS_ENSURE_ARG_POINTER(aEntryCount);
218 *aEntryCount = mDevice->getEntryCount();
219 return NS_OK;
222 /* readonly attribute unsigned long totalSize; */
223 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
225 NS_ENSURE_ARG_POINTER(aTotalSize);
226 // Returned unit's are in bytes
227 *aTotalSize = mDevice->getCacheSize() * 1024;
228 return NS_OK;
231 /* readonly attribute unsigned long maximumSize; */
232 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
234 NS_ENSURE_ARG_POINTER(aMaximumSize);
235 // Returned unit's are in bytes
236 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
237 return NS_OK;
241 /******************************************************************************
242 * nsDiskCache
243 *****************************************************************************/
246 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
248 * See http://burtleburtle.net/bob/hash/evahash.html for more information
249 * about this hash function.
251 * This algorithm of this method implies nsDiskCacheRecords will be stored
252 * in a certain order on disk. If the algorithm changes, existing cache
253 * map files may become invalid, and therefore the kCurrentVersion needs
254 * to be revised.
257 static inline void hashmix(PRUint32& a, PRUint32& b, PRUint32& c)
259 a -= b; a -= c; a ^= (c>>13);
260 b -= c; b -= a; b ^= (a<<8);
261 c -= a; c -= b; c ^= (b>>13);
262 a -= b; a -= c; a ^= (c>>12);
263 b -= c; b -= a; b ^= (a<<16);
264 c -= a; c -= b; c ^= (b>>5);
265 a -= b; a -= c; a ^= (c>>3);
266 b -= c; b -= a; b ^= (a<<10);
267 c -= a; c -= b; c ^= (b>>15);
270 PLDHashNumber
271 nsDiskCache::Hash(const char * key, PLDHashNumber initval)
273 const PRUint8 *k = reinterpret_cast<const PRUint8*>(key);
274 PRUint32 a, b, c, len, length;
276 length = PL_strlen(key);
277 /* Set up the internal state */
278 len = length;
279 a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
280 c = initval; /* variable initialization of internal state */
282 /*---------------------------------------- handle most of the key */
283 while (len >= 12)
285 a += k[0] + (PRUint32(k[1])<<8) + (PRUint32(k[2])<<16) + (PRUint32(k[3])<<24);
286 b += k[4] + (PRUint32(k[5])<<8) + (PRUint32(k[6])<<16) + (PRUint32(k[7])<<24);
287 c += k[8] + (PRUint32(k[9])<<8) + (PRUint32(k[10])<<16) + (PRUint32(k[11])<<24);
288 hashmix(a, b, c);
289 k += 12; len -= 12;
292 /*------------------------------------- handle the last 11 bytes */
293 c += length;
294 switch(len) { /* all the case statements fall through */
295 case 11: c += (PRUint32(k[10])<<24);
296 case 10: c += (PRUint32(k[9])<<16);
297 case 9 : c += (PRUint32(k[8])<<8);
298 /* the low-order byte of c is reserved for the length */
299 case 8 : b += (PRUint32(k[7])<<24);
300 case 7 : b += (PRUint32(k[6])<<16);
301 case 6 : b += (PRUint32(k[5])<<8);
302 case 5 : b += k[4];
303 case 4 : a += (PRUint32(k[3])<<24);
304 case 3 : a += (PRUint32(k[2])<<16);
305 case 2 : a += (PRUint32(k[1])<<8);
306 case 1 : a += k[0];
307 /* case 0: nothing left to add */
309 hashmix(a, b, c);
311 return c;
314 nsresult
315 nsDiskCache::Truncate(PRFileDesc * fd, PRUint32 newEOF)
317 // use modified SetEOF from nsFileStreams::SetEOF()
319 #if defined(XP_UNIX) || defined(XP_BEOS)
320 if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
321 NS_ERROR("ftruncate failed");
322 return NS_ERROR_FAILURE;
325 #elif defined(XP_WIN)
326 PRInt32 cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
327 if (cnt == -1) return NS_ERROR_FAILURE;
328 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
329 NS_ERROR("SetEndOfFile failed");
330 return NS_ERROR_FAILURE;
333 #elif defined(XP_OS2)
334 if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(fd), newEOF) != NO_ERROR) {
335 NS_ERROR("DosSetFileSize failed");
336 return NS_ERROR_FAILURE;
338 #else
339 // add implementations for other platforms here
340 #endif
341 return NS_OK;
345 /******************************************************************************
346 * nsDiskCacheDevice
347 *****************************************************************************/
349 nsDiskCacheDevice::nsDiskCacheDevice()
350 : mCacheCapacity(0)
351 , mInitialized(PR_FALSE)
355 nsDiskCacheDevice::~nsDiskCacheDevice()
357 Shutdown();
362 * methods of nsCacheDevice
364 nsresult
365 nsDiskCacheDevice::Init()
367 NS_TIME_FUNCTION;
369 nsresult rv;
371 if (Initialized()) {
372 NS_ERROR("Disk cache already initialized!");
373 return NS_ERROR_UNEXPECTED;
376 if (!mCacheDirectory)
377 return NS_ERROR_FAILURE;
379 rv = mBindery.Init();
380 if (NS_FAILED(rv))
381 return rv;
383 // Open Disk Cache
384 rv = OpenDiskCache();
385 if (NS_FAILED(rv)) {
386 (void) mCacheMap.Close(PR_FALSE);
387 return rv;
390 mInitialized = PR_TRUE;
391 return NS_OK;
396 * NOTE: called while holding the cache service lock
398 nsresult
399 nsDiskCacheDevice::Shutdown()
401 nsresult rv = Shutdown_Private(PR_TRUE);
402 if (NS_FAILED(rv))
403 return rv;
405 if (mCacheDirectory) {
406 // delete any trash files left-over before shutting down.
407 nsCOMPtr<nsIFile> trashDir;
408 GetTrashDir(mCacheDirectory, &trashDir);
409 if (trashDir) {
410 PRBool exists;
411 if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
412 DeleteDir(trashDir, PR_FALSE, PR_TRUE);
416 return NS_OK;
420 nsresult
421 nsDiskCacheDevice::Shutdown_Private(PRBool flush)
423 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
425 if (Initialized()) {
426 // check cache limits in case we need to evict.
427 EvictDiskCacheEntries(mCacheCapacity);
429 // write out persistent information about the cache.
430 (void) mCacheMap.Close(flush);
432 mBindery.Reset();
434 mInitialized = PR_FALSE;
437 return NS_OK;
441 const char *
442 nsDiskCacheDevice::GetDeviceID()
444 return DISK_CACHE_DEVICE_ID;
449 * FindEntry -
451 * cases: key not in disk cache, hash number free
452 * key not in disk cache, hash number used
453 * key in disk cache
455 * NOTE: called while holding the cache service lock
457 nsCacheEntry *
458 nsDiskCacheDevice::FindEntry(nsCString * key, PRBool *collision)
460 if (!Initialized()) return nsnull; // NS_ERROR_NOT_INITIALIZED
461 nsDiskCacheRecord record;
462 nsDiskCacheBinding * binding = nsnull;
463 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
465 *collision = PR_FALSE;
467 binding = mBindery.FindActiveBinding(hashNumber);
468 if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
469 *collision = PR_TRUE;
470 return nsnull;
472 binding = nsnull;
474 // lookup hash number in cache map
475 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
476 if (NS_FAILED(rv)) return nsnull; // XXX log error?
478 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
479 if (!diskEntry) return nsnull;
481 // compare key to be sure
482 if (!key->Equals(diskEntry->Key())) {
483 *collision = PR_TRUE;
484 return nsnull;
487 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
488 if (!entry) return nsnull;
490 binding = mBindery.CreateBinding(entry, &record);
491 if (!binding) {
492 delete entry;
493 return nsnull;
496 return entry;
501 * NOTE: called while holding the cache service lock
503 nsresult
504 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
506 nsresult rv = NS_OK;
507 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
508 NS_ASSERTION(binding, "DeactivateEntry: binding == nsnull");
509 if (!binding) return NS_ERROR_UNEXPECTED;
511 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
512 entry, binding->mRecord.HashNumber()));
514 if (entry->IsDoomed()) {
515 // delete data, entry, record from disk for entry
516 rv = mCacheMap.DeleteStorage(&binding->mRecord);
518 } else {
519 // save stuff to disk for entry
520 rv = mCacheMap.WriteDiskCacheEntry(binding);
521 if (NS_FAILED(rv)) {
522 // clean up as best we can
523 (void) mCacheMap.DeleteStorage(&binding->mRecord);
524 (void) mCacheMap.DeleteRecord(&binding->mRecord);
525 binding->mDoomed = PR_TRUE; // record is no longer in cache map
529 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
530 delete entry; // which will release binding
531 return rv;
536 * BindEntry()
537 * no hash number collision -> no problem
538 * collision
539 * record not active -> evict, no problem
540 * record is active
541 * record is already doomed -> record shouldn't have been in map, no problem
542 * record is not doomed -> doom, and replace record in map
544 * walk matching hashnumber list to find lowest generation number
545 * take generation number from other (data/meta) location,
546 * or walk active list
548 * NOTE: called while holding the cache service lock
550 nsresult
551 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
553 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
554 nsresult rv = NS_OK;
555 nsDiskCacheRecord record, oldRecord;
556 nsDiskCacheBinding *binding;
557 PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
559 // Find out if there is already an active binding for this hash. If yes it
560 // should have another key since BindEntry() shouldn't be called twice for
561 // the same entry. Doom the old entry, the new one will get another
562 // generation number so files won't collide.
563 binding = mBindery.FindActiveBinding(hashNumber);
564 if (binding) {
565 NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
566 "BindEntry called for already bound entry!");
567 nsCacheService::DoomEntry(binding->mCacheEntry);
568 binding = nsnull;
571 // Lookup hash number in cache map. There can be a colliding inactive entry.
572 // See bug #321361 comment 21 for the scenario. If there is such entry,
573 // delete it.
574 rv = mCacheMap.FindRecord(hashNumber, &record);
575 if (NS_SUCCEEDED(rv)) {
576 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
577 if (diskEntry) {
578 // compare key to be sure
579 if (!entry->Key()->Equals(diskEntry->Key())) {
580 mCacheMap.DeleteStorage(&record);
581 rv = mCacheMap.DeleteRecord(&record);
582 if (NS_FAILED(rv)) return rv;
585 record = nsDiskCacheRecord();
588 // create a new record for this entry
589 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
590 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
592 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
593 entry, record.HashNumber()));
595 if (!entry->IsDoomed()) {
596 // if entry isn't doomed, add it to the cache map
597 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
598 if (NS_FAILED(rv)) return rv;
600 PRUint32 oldHashNumber = oldRecord.HashNumber();
601 if (oldHashNumber) {
602 // gotta evict this one first
603 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
604 if (oldBinding) {
605 // XXX if debug : compare keys for hashNumber collision
607 if (!oldBinding->mCacheEntry->IsDoomed()) {
608 // we've got a live one!
609 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
610 // storage will be delete when oldBinding->mCacheEntry is Deactivated
612 } else {
613 // delete storage
614 // XXX if debug : compare keys for hashNumber collision
615 rv = mCacheMap.DeleteStorage(&oldRecord);
616 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
621 // Make sure this entry has its associated nsDiskCacheBinding attached.
622 binding = mBindery.CreateBinding(entry, &record);
623 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
624 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
625 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
627 return NS_OK;
632 * NOTE: called while holding the cache service lock
634 void
635 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
637 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
639 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
640 NS_ASSERTION(binding, "DoomEntry: binding == nsnull");
641 if (!binding) return;
643 if (!binding->mDoomed) {
644 // so it can't be seen by FindEntry() ever again.
645 #ifdef DEBUG
646 nsresult rv =
647 #endif
648 mCacheMap.DeleteRecord(&binding->mRecord);
649 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
650 binding->mDoomed = PR_TRUE; // record in no longer in cache map
656 * NOTE: called while holding the cache service lock
658 nsresult
659 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
660 nsCacheAccessMode mode,
661 PRUint32 offset,
662 nsIInputStream ** result)
664 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
665 entry, mode, offset));
667 NS_ENSURE_ARG_POINTER(entry);
668 NS_ENSURE_ARG_POINTER(result);
670 nsresult rv;
671 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
672 NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
674 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
676 rv = binding->EnsureStreamIO();
677 if (NS_FAILED(rv)) return rv;
679 return binding->mStreamIO->GetInputStream(offset, result);
684 * NOTE: called while holding the cache service lock
686 nsresult
687 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
688 nsCacheAccessMode mode,
689 PRUint32 offset,
690 nsIOutputStream ** result)
692 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
693 entry, mode, offset));
695 NS_ENSURE_ARG_POINTER(entry);
696 NS_ENSURE_ARG_POINTER(result);
698 nsresult rv;
699 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
700 NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
702 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
704 rv = binding->EnsureStreamIO();
705 if (NS_FAILED(rv)) return rv;
707 return binding->mStreamIO->GetOutputStream(offset, result);
712 * NOTE: called while holding the cache service lock
714 nsresult
715 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
716 nsIFile ** result)
718 NS_ENSURE_ARG_POINTER(result);
719 *result = nsnull;
721 nsresult rv;
723 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
724 if (!binding) {
725 NS_WARNING("GetFileForEntry: binding == nsnull");
726 return NS_ERROR_UNEXPECTED;
729 // check/set binding->mRecord for separate file, sync w/mCacheMap
730 if (binding->mRecord.DataLocationInitialized()) {
731 if (binding->mRecord.DataFile() != 0)
732 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
734 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
735 } else {
736 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
737 binding->mRecord.SetDataFileSize(0); // 1k minimum
738 if (!binding->mDoomed) {
739 // record stored in cache map, so update it
740 rv = mCacheMap.UpdateRecord(&binding->mRecord);
741 if (NS_FAILED(rv)) return rv;
745 nsCOMPtr<nsIFile> file;
746 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
747 nsDiskCache::kData,
748 PR_FALSE,
749 getter_AddRefs(file));
750 if (NS_FAILED(rv)) return rv;
752 NS_IF_ADDREF(*result = file);
753 return NS_OK;
758 * This routine will get called every time an open descriptor is written to.
760 * NOTE: called while holding the cache service lock
762 nsresult
763 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
765 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
766 entry, deltaSize));
768 // If passed a negative value, then there's nothing to do.
769 if (deltaSize < 0)
770 return NS_OK;
772 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
773 NS_ASSERTION(binding, "OnDataSizeChange: binding == nsnull");
774 if (!binding) return NS_ERROR_UNEXPECTED;
776 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
778 PRUint32 newSize = entry->DataSize() + deltaSize;
779 PRUint32 newSizeK = ((newSize + 0x3FF) >> 10);
781 // If the new size is larger than max. file size or larger than
782 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort
783 if (EntryIsTooBig(newSize)) {
784 #ifdef DEBUG
785 nsresult rv =
786 #endif
787 nsCacheService::DoomEntry(entry);
788 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
789 return NS_ERROR_ABORT;
792 PRUint32 sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
794 NS_ASSERTION(sizeK <= USHRT_MAX, "data size out of range");
795 NS_ASSERTION(newSizeK <= USHRT_MAX, "data size out of range");
797 // pre-evict entries to make space for new data
798 PRUint32 targetCapacity = mCacheCapacity > (newSizeK - sizeK)
799 ? mCacheCapacity - (newSizeK - sizeK)
800 : 0;
801 EvictDiskCacheEntries(targetCapacity);
803 return NS_OK;
807 /******************************************************************************
808 * EntryInfoVisitor
809 *****************************************************************************/
810 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
812 public:
813 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
814 nsICacheVisitor * visitor)
815 : mCacheMap(cacheMap)
816 , mVisitor(visitor)
819 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord)
821 // XXX optimization: do we have this record in memory?
823 // read in the entry (metadata)
824 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
825 if (!diskEntry) {
826 return kVisitNextRecord;
829 // create nsICacheEntryInfo
830 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
831 if (!entryInfo) {
832 return kStopVisitingRecords;
834 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
836 PRBool keepGoing;
837 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
838 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
841 private:
842 nsDiskCacheMap * mCacheMap;
843 nsICacheVisitor * mVisitor;
847 nsresult
848 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
850 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
851 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
852 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
854 PRBool keepGoing;
855 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
856 if (NS_FAILED(rv)) return rv;
858 if (keepGoing) {
859 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
860 return mCacheMap.VisitRecords(&infoVisitor);
863 return NS_OK;
866 // Max allowed size for an entry is currently MIN(5MB, 1/8 CacheCapacity)
867 bool
868 nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize)
870 return entrySize > kMaxDataFileSize
871 || entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
874 nsresult
875 nsDiskCacheDevice::EvictEntries(const char * clientID)
877 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
879 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
880 nsresult rv;
882 if (clientID == nsnull) {
883 // we're clearing the entire disk cache
884 rv = ClearDiskCache();
885 if (rv != NS_ERROR_CACHE_IN_USE)
886 return rv;
889 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
890 rv = mCacheMap.VisitRecords(&evictor);
892 if (clientID == nsnull) // we tried to clear the entire cache
893 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
894 return rv;
899 * private methods
902 nsresult
903 nsDiskCacheDevice::OpenDiskCache()
905 // if we don't have a cache directory, create one and open it
906 PRBool exists;
907 nsresult rv = mCacheDirectory->Exists(&exists);
908 if (NS_FAILED(rv))
909 return rv;
911 PRBool trashing = PR_FALSE;
912 if (exists) {
913 // Try opening cache map file.
914 rv = mCacheMap.Open(mCacheDirectory);
915 // move "corrupt" caches to trash
916 if (rv == NS_ERROR_FILE_CORRUPTED) {
917 rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
918 if (NS_FAILED(rv))
919 return rv;
920 exists = PR_FALSE;
921 trashing = PR_TRUE;
923 else if (NS_FAILED(rv))
924 return rv;
927 // if we don't have a cache directory, create one and open it
928 if (!exists) {
929 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
930 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
931 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
932 if (NS_FAILED(rv))
933 return rv;
935 // reopen the cache map
936 rv = mCacheMap.Open(mCacheDirectory);
937 if (NS_FAILED(rv))
938 return rv;
941 if (!trashing) {
942 // delete any trash files leftover from a previous run
943 nsCOMPtr<nsIFile> trashDir;
944 GetTrashDir(mCacheDirectory, &trashDir);
945 if (trashDir) {
946 PRBool exists;
947 if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
948 DeleteDir(trashDir, PR_FALSE, PR_FALSE);
952 return NS_OK;
956 nsresult
957 nsDiskCacheDevice::ClearDiskCache()
959 if (mBindery.ActiveBindings())
960 return NS_ERROR_CACHE_IN_USE;
962 nsresult rv = Shutdown_Private(PR_FALSE); // false: don't bother flushing
963 if (NS_FAILED(rv))
964 return rv;
966 // If the disk cache directory is already gone, then it's not an error if
967 // we fail to delete it ;-)
968 rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
969 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
970 return rv;
972 return Init();
976 nsresult
977 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 targetCapacity)
979 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
980 targetCapacity));
982 NS_ASSERTION(targetCapacity > 0, "oops");
984 if (mCacheMap.TotalSize() < targetCapacity)
985 return NS_OK;
987 // targetCapacity is in KiB's
988 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nsnull);
989 return mCacheMap.EvictRecords(&evictor);
994 * methods for prefs
997 void
998 nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile * parentDir)
1000 nsresult rv;
1001 PRBool exists;
1003 if (Initialized()) {
1004 NS_ASSERTION(PR_FALSE, "Cannot switch cache directory when initialized");
1005 return;
1008 if (!parentDir) {
1009 mCacheDirectory = nsnull;
1010 return;
1013 // ensure parent directory exists
1014 rv = parentDir->Exists(&exists);
1015 if (NS_SUCCEEDED(rv) && !exists)
1016 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1017 if (NS_FAILED(rv)) return;
1019 // ensure cache directory exists
1020 nsCOMPtr<nsIFile> directory;
1022 rv = parentDir->Clone(getter_AddRefs(directory));
1023 if (NS_FAILED(rv)) return;
1024 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
1025 if (NS_FAILED(rv)) return;
1027 mCacheDirectory = do_QueryInterface(directory);
1031 void
1032 nsDiskCacheDevice::getCacheDirectory(nsILocalFile ** result)
1034 *result = mCacheDirectory;
1035 NS_IF_ADDREF(*result);
1040 * NOTE: called while holding the cache service lock
1042 void
1043 nsDiskCacheDevice::SetCapacity(PRUint32 capacity)
1045 // Units are KiB's
1046 mCacheCapacity = capacity;
1047 if (Initialized()) {
1048 // start evicting entries if the new size is smaller!
1049 EvictDiskCacheEntries(mCacheCapacity);
1051 // Let cache map know of the new capacity
1052 mCacheMap.NotifyCapacityChange(capacity);
1056 PRUint32 nsDiskCacheDevice::getCacheCapacity()
1058 return mCacheCapacity;
1062 PRUint32 nsDiskCacheDevice::getCacheSize()
1064 return mCacheMap.TotalSize();
1068 PRUint32 nsDiskCacheDevice::getEntryCount()
1070 return mCacheMap.EntryCount();