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
16 * The Original Code is nsDiskCacheDevice.cpp, released
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.
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 ***** */
44 // include files for ftruncate (or equivalent)
45 #if defined(XP_UNIX) || defined(XP_BEOS)
50 #define INCL_DOSERRORS
53 // XXX add necessary include file for ftruncate (or equivalent)
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"
72 #include "nsDeleteDir.h"
74 #include "nsICacheVisitor.h"
75 #include "nsReadableUtils.h"
76 #include "nsIInputStream.h"
77 #include "nsIOutputStream.h"
78 #include "nsAutoLock.h"
80 #include "nsCOMArray.h"
81 #include "nsISimpleEnumerator.h"
83 #include "mozilla/FunctionTimer.h"
85 static const char DISK_CACHE_DEVICE_ID
[] = { "disk" };
88 /******************************************************************************
91 * Helper class for nsDiskCacheDevice.
93 *****************************************************************************/
95 class nsDiskCacheEvictor
: public nsDiskCacheRecordVisitor
98 nsDiskCacheEvictor( nsDiskCacheMap
* cacheMap
,
99 nsDiskCacheBindery
* cacheBindery
,
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
);
113 nsDiskCacheMap
* mCacheMap
;
114 nsDiskCacheBindery
* mBindery
;
115 PRUint32 mTargetSize
;
116 const char * mClientID
;
117 PRUint32 mClientIDSize
;
122 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord
* mapRecord
)
124 if (mCacheMap
->TotalSize() < mTargetSize
)
125 return kStopVisitingRecords
;
128 // we're just evicting records for a specific client
129 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
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());
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
);
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
{
164 NS_DECL_NSICACHEDEVICEINFO
166 nsDiskCacheDeviceInfo(nsDiskCacheDevice
* device
)
171 virtual ~nsDiskCacheDeviceInfo() {}
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
);
193 buffer
.AssignLiteral(" <tr>\n"
194 " <th>Cache Directory:</th>\n"
196 nsCOMPtr
<nsILocalFile
> cacheDir
;
198 mDevice
->getCacheDirectory(getter_AddRefs(cacheDir
));
199 nsresult rv
= cacheDir
->GetPath(path
);
200 if (NS_SUCCEEDED(rv
)) {
201 AppendUTF16toUTF8(path
, buffer
);
203 buffer
.AppendLiteral("directory unavailable");
205 buffer
.AppendLiteral("</td>\n"
208 *usageReport
= ToNewCString(buffer
);
209 if (!*usageReport
) return NS_ERROR_OUT_OF_MEMORY
;
214 /* readonly attribute unsigned long entryCount; */
215 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetEntryCount(PRUint32
*aEntryCount
)
217 NS_ENSURE_ARG_POINTER(aEntryCount
);
218 *aEntryCount
= mDevice
->getEntryCount();
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;
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;
241 /******************************************************************************
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
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);
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 */
279 a
= b
= 0x9e3779b9; /* the golden ratio; an arbitrary value */
280 c
= initval
; /* variable initialization of internal state */
282 /*---------------------------------------- handle most of the key */
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);
292 /*------------------------------------- handle the last 11 bytes */
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);
303 case 4 : a
+= (PRUint32(k
[3])<<24);
304 case 3 : a
+= (PRUint32(k
[2])<<16);
305 case 2 : a
+= (PRUint32(k
[1])<<8);
307 /* case 0: nothing left to add */
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
;
339 // add implementations for other platforms here
345 /******************************************************************************
347 *****************************************************************************/
349 nsDiskCacheDevice::nsDiskCacheDevice()
351 , mInitialized(PR_FALSE
)
355 nsDiskCacheDevice::~nsDiskCacheDevice()
362 * methods of nsCacheDevice
365 nsDiskCacheDevice::Init()
372 NS_ERROR("Disk cache already initialized!");
373 return NS_ERROR_UNEXPECTED
;
376 if (!mCacheDirectory
)
377 return NS_ERROR_FAILURE
;
379 rv
= mBindery
.Init();
384 rv
= OpenDiskCache();
386 (void) mCacheMap
.Close(PR_FALSE
);
390 mInitialized
= PR_TRUE
;
396 * NOTE: called while holding the cache service lock
399 nsDiskCacheDevice::Shutdown()
401 nsresult rv
= Shutdown_Private(PR_TRUE
);
405 if (mCacheDirectory
) {
406 // delete any trash files left-over before shutting down.
407 nsCOMPtr
<nsIFile
> trashDir
;
408 GetTrashDir(mCacheDirectory
, &trashDir
);
411 if (NS_SUCCEEDED(trashDir
->Exists(&exists
)) && exists
)
412 DeleteDir(trashDir
, PR_FALSE
, PR_TRUE
);
421 nsDiskCacheDevice::Shutdown_Private(PRBool flush
)
423 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush
));
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
);
434 mInitialized
= PR_FALSE
;
442 nsDiskCacheDevice::GetDeviceID()
444 return DISK_CACHE_DEVICE_ID
;
451 * cases: key not in disk cache, hash number free
452 * key not in disk cache, hash number used
455 * NOTE: called while holding the cache service lock
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
;
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
;
487 nsCacheEntry
* entry
= diskEntry
->CreateCacheEntry(this);
488 if (!entry
) return nsnull
;
490 binding
= mBindery
.CreateBinding(entry
, &record
);
501 * NOTE: called while holding the cache service lock
504 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry
* entry
)
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
);
519 // save stuff to disk for entry
520 rv
= mCacheMap
.WriteDiskCacheEntry(binding
);
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
537 * no hash number collision -> no problem
539 * record not active -> evict, no problem
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
551 nsDiskCacheDevice::BindEntry(nsCacheEntry
* entry
)
553 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
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
);
565 NS_ASSERTION(!binding
->mCacheEntry
->Key()->Equals(*entry
->Key()),
566 "BindEntry called for already bound entry!");
567 nsCacheService::DoomEntry(binding
->mCacheEntry
);
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,
574 rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
575 if (NS_SUCCEEDED(rv
)) {
576 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
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();
602 // gotta evict this one first
603 nsDiskCacheBinding
* oldBinding
= mBindery
.FindActiveBinding(oldHashNumber
);
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
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");
632 * NOTE: called while holding the cache service lock
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.
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
659 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry
* entry
,
660 nsCacheAccessMode mode
,
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
);
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
687 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry
* entry
,
688 nsCacheAccessMode mode
,
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
);
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
715 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry
* entry
,
718 NS_ENSURE_ARG_POINTER(result
);
723 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
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");
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
,
749 getter_AddRefs(file
));
750 if (NS_FAILED(rv
)) return rv
;
752 NS_IF_ADDREF(*result
= file
);
758 * This routine will get called every time an open descriptor is written to.
760 * NOTE: called while holding the cache service lock
763 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry
* entry
, PRInt32 deltaSize
)
765 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
768 // If passed a negative value, then there's nothing to do.
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
)) {
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
)
801 EvictDiskCacheEntries(targetCapacity
);
807 /******************************************************************************
809 *****************************************************************************/
810 class EntryInfoVisitor
: public nsDiskCacheRecordVisitor
813 EntryInfoVisitor(nsDiskCacheMap
* cacheMap
,
814 nsICacheVisitor
* visitor
)
815 : mCacheMap(cacheMap
)
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
);
826 return kVisitNextRecord
;
829 // create nsICacheEntryInfo
830 nsDiskCacheEntryInfo
* entryInfo
= new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID
, diskEntry
);
832 return kStopVisitingRecords
;
834 nsCOMPtr
<nsICacheEntryInfo
> ref(entryInfo
);
837 (void)mVisitor
->VisitEntry(DISK_CACHE_DEVICE_ID
, entryInfo
, &keepGoing
);
838 return keepGoing
? kVisitNextRecord
: kStopVisitingRecords
;
842 nsDiskCacheMap
* mCacheMap
;
843 nsICacheVisitor
* mVisitor
;
848 nsDiskCacheDevice::Visit(nsICacheVisitor
* visitor
)
850 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
851 nsDiskCacheDeviceInfo
* deviceInfo
= new nsDiskCacheDeviceInfo(this);
852 nsCOMPtr
<nsICacheDeviceInfo
> ref(deviceInfo
);
855 nsresult rv
= visitor
->VisitDevice(DISK_CACHE_DEVICE_ID
, deviceInfo
, &keepGoing
);
856 if (NS_FAILED(rv
)) return rv
;
859 EntryInfoVisitor
infoVisitor(&mCacheMap
, visitor
);
860 return mCacheMap
.VisitRecords(&infoVisitor
);
866 // Max allowed size for an entry is currently MIN(5MB, 1/8 CacheCapacity)
868 nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize
)
870 return entrySize
> kMaxDataFileSize
871 || entrySize
> (static_cast<PRInt64
>(mCacheCapacity
) * 1024 / 8);
875 nsDiskCacheDevice::EvictEntries(const char * clientID
)
877 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID
));
879 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
882 if (clientID
== nsnull
) {
883 // we're clearing the entire disk cache
884 rv
= ClearDiskCache();
885 if (rv
!= NS_ERROR_CACHE_IN_USE
)
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)
903 nsDiskCacheDevice::OpenDiskCache()
905 // if we don't have a cache directory, create one and open it
907 nsresult rv
= mCacheDirectory
->Exists(&exists
);
911 PRBool trashing
= PR_FALSE
;
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
);
923 else if (NS_FAILED(rv
))
927 // if we don't have a cache directory, create one and open it
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
));
935 // reopen the cache map
936 rv
= mCacheMap
.Open(mCacheDirectory
);
942 // delete any trash files leftover from a previous run
943 nsCOMPtr
<nsIFile
> trashDir
;
944 GetTrashDir(mCacheDirectory
, &trashDir
);
947 if (NS_SUCCEEDED(trashDir
->Exists(&exists
)) && exists
)
948 DeleteDir(trashDir
, PR_FALSE
, PR_FALSE
);
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
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
)
977 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 targetCapacity
)
979 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
982 NS_ASSERTION(targetCapacity
> 0, "oops");
984 if (mCacheMap
.TotalSize() < targetCapacity
)
987 // targetCapacity is in KiB's
988 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, targetCapacity
, nsnull
);
989 return mCacheMap
.EvictRecords(&evictor
);
998 nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile
* parentDir
)
1003 if (Initialized()) {
1004 NS_ASSERTION(PR_FALSE
, "Cannot switch cache directory when initialized");
1009 mCacheDirectory
= nsnull
;
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
);
1032 nsDiskCacheDevice::getCacheDirectory(nsILocalFile
** result
)
1034 *result
= mCacheDirectory
;
1035 NS_IF_ADDREF(*result
);
1040 * NOTE: called while holding the cache service lock
1043 nsDiskCacheDevice::SetCapacity(PRUint32 capacity
)
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();