Bug 458861. Validate TrueType headers before activating downloaded font. r=roc, sr...
[wine-gecko.git] / netwerk / cache / src / nsDiskCacheDevice.cpp
blob60e4039151cdcf8ef7d934f483f2435c849460d9
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 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
86 /******************************************************************************
87 * nsDiskCacheEvictor
89 * Helper class for nsDiskCacheDevice.
91 *****************************************************************************/
93 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
95 public:
96 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
97 nsDiskCacheBindery * cacheBindery,
98 PRUint32 targetSize,
99 const char * clientID)
100 : mCacheMap(cacheMap)
101 , mBindery(cacheBindery)
102 , mTargetSize(targetSize)
103 , mClientID(clientID)
105 mClientIDSize = clientID ? strlen(clientID) : 0;
108 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord);
110 private:
111 nsDiskCacheMap * mCacheMap;
112 nsDiskCacheBindery * mBindery;
113 PRUint32 mTargetSize;
114 const char * mClientID;
115 PRUint32 mClientIDSize;
119 PRInt32
120 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
122 if (mCacheMap->TotalSize() < mTargetSize)
123 return kStopVisitingRecords;
125 if (mClientID) {
126 // we're just evicting records for a specific client
127 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
128 if (!diskEntry)
129 return kVisitNextRecord; // XXX or delete record?
131 // Compare clientID's without malloc
132 if ((diskEntry->mKeySize <= mClientIDSize) ||
133 (diskEntry->Key()[mClientIDSize] != ':') ||
134 (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
135 return kVisitNextRecord; // clientID doesn't match, skip it
139 nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
140 if (binding) {
141 // We are currently using this entry, so all we can do is doom it.
142 // Since we're enumerating the records, we don't want to call
143 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
144 binding->mDoomed = PR_TRUE; // mark binding record as 'deleted'
145 nsCacheService::DoomEntry(binding->mCacheEntry);
146 } else {
147 // entry not in use, just delete storage because we're enumerating the records
148 (void) mCacheMap->DeleteStorage(mapRecord);
151 return kDeleteRecordAndContinue; // this will REALLY delete the record
155 /******************************************************************************
156 * nsDiskCacheDeviceInfo
157 *****************************************************************************/
159 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
160 public:
161 NS_DECL_ISUPPORTS
162 NS_DECL_NSICACHEDEVICEINFO
164 nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
165 : mDevice(device)
169 virtual ~nsDiskCacheDeviceInfo() {}
171 private:
172 nsDiskCacheDevice* mDevice;
175 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
177 /* readonly attribute string description; */
178 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
180 NS_ENSURE_ARG_POINTER(aDescription);
181 *aDescription = NS_strdup("Disk cache device");
182 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
185 /* readonly attribute string usageReport; */
186 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
188 NS_ENSURE_ARG_POINTER(usageReport);
189 nsCString buffer;
191 buffer.AssignLiteral("\n<tr>\n<td><b>Cache Directory:</b></td>\n<td><tt> ");
192 nsCOMPtr<nsILocalFile> cacheDir;
193 nsAutoString path;
194 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
195 nsresult rv = cacheDir->GetPath(path);
196 if (NS_SUCCEEDED(rv)) {
197 AppendUTF16toUTF8(path, buffer);
198 } else {
199 buffer.AppendLiteral("directory unavailable");
201 buffer.AppendLiteral("</tt></td>\n</tr>\n");
202 // buffer.Append("<tr><td><b>Files:</b></td><td><tt> XXX</tt></td></tr>");
203 *usageReport = ToNewCString(buffer);
204 if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
206 return NS_OK;
209 /* readonly attribute unsigned long entryCount; */
210 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
212 NS_ENSURE_ARG_POINTER(aEntryCount);
213 *aEntryCount = mDevice->getEntryCount();
214 return NS_OK;
217 /* readonly attribute unsigned long totalSize; */
218 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
220 NS_ENSURE_ARG_POINTER(aTotalSize);
221 // Returned unit's are in bytes
222 *aTotalSize = mDevice->getCacheSize() * 1024;
223 return NS_OK;
226 /* readonly attribute unsigned long maximumSize; */
227 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
229 NS_ENSURE_ARG_POINTER(aMaximumSize);
230 // Returned unit's are in bytes
231 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
232 return NS_OK;
236 /******************************************************************************
237 * nsDiskCache
238 *****************************************************************************/
241 * nsDiskCache::Hash(const char * key)
243 * This algorithm of this method implies nsDiskCacheRecords will be stored
244 * in a certain order on disk. If the algorithm changes, existing cache
245 * map files may become invalid, and therefore the kCurrentVersion needs
246 * to be revised.
248 PLDHashNumber
249 nsDiskCache::Hash(const char * key)
251 PLDHashNumber h = 0;
252 for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s)
253 h = PR_ROTATE_LEFT32(h, 4) ^ *s;
254 return (h == 0 ? ULONG_MAX : h);
258 nsresult
259 nsDiskCache::Truncate(PRFileDesc * fd, PRUint32 newEOF)
261 // use modified SetEOF from nsFileStreams::SetEOF()
263 #if defined(XP_UNIX) || defined(XP_BEOS)
264 if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
265 NS_ERROR("ftruncate failed");
266 return NS_ERROR_FAILURE;
269 #elif defined(XP_WIN)
270 PRInt32 cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
271 if (cnt == -1) return NS_ERROR_FAILURE;
272 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
273 NS_ERROR("SetEndOfFile failed");
274 return NS_ERROR_FAILURE;
277 #elif defined(XP_OS2)
278 if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(fd), newEOF) != NO_ERROR) {
279 NS_ERROR("DosSetFileSize failed");
280 return NS_ERROR_FAILURE;
282 #else
283 // add implementations for other platforms here
284 #endif
285 return NS_OK;
289 /******************************************************************************
290 * nsDiskCacheDevice
291 *****************************************************************************/
293 nsDiskCacheDevice::nsDiskCacheDevice()
294 : mCacheCapacity(0)
295 , mInitialized(PR_FALSE)
299 nsDiskCacheDevice::~nsDiskCacheDevice()
301 Shutdown();
306 * methods of nsCacheDevice
308 nsresult
309 nsDiskCacheDevice::Init()
311 nsresult rv;
313 NS_ENSURE_TRUE(!Initialized(), NS_ERROR_FAILURE);
315 if (!mCacheDirectory)
316 return NS_ERROR_FAILURE;
318 rv = mBindery.Init();
319 if (NS_FAILED(rv))
320 return rv;
322 // Open Disk Cache
323 rv = OpenDiskCache();
324 if (NS_FAILED(rv)) {
325 (void) mCacheMap.Close(PR_FALSE);
326 return rv;
329 mInitialized = PR_TRUE;
330 return NS_OK;
335 * NOTE: called while holding the cache service lock
337 nsresult
338 nsDiskCacheDevice::Shutdown()
340 nsresult rv = Shutdown_Private(PR_TRUE);
341 if (NS_FAILED(rv))
342 return rv;
344 if (mCacheDirectory) {
345 // delete any trash files left-over before shutting down.
346 nsCOMPtr<nsIFile> trashDir;
347 GetTrashDir(mCacheDirectory, &trashDir);
348 if (trashDir) {
349 PRBool exists;
350 if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
351 DeleteDir(trashDir, PR_FALSE, PR_TRUE);
355 return NS_OK;
359 nsresult
360 nsDiskCacheDevice::Shutdown_Private(PRBool flush)
362 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
364 if (Initialized()) {
365 // check cache limits in case we need to evict.
366 EvictDiskCacheEntries(mCacheCapacity);
368 // write out persistent information about the cache.
369 (void) mCacheMap.Close(flush);
371 mBindery.Reset();
373 mInitialized = PR_FALSE;
376 return NS_OK;
380 const char *
381 nsDiskCacheDevice::GetDeviceID()
383 return DISK_CACHE_DEVICE_ID;
388 * FindEntry -
390 * cases: key not in disk cache, hash number free
391 * key not in disk cache, hash number used
392 * key in disk cache
394 * NOTE: called while holding the cache service lock
396 nsCacheEntry *
397 nsDiskCacheDevice::FindEntry(nsCString * key, PRBool *collision)
399 if (!Initialized()) return nsnull; // NS_ERROR_NOT_INITIALIZED
400 nsDiskCacheRecord record;
401 nsDiskCacheBinding * binding = nsnull;
402 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
404 *collision = PR_FALSE;
406 binding = mBindery.FindActiveBinding(hashNumber);
407 if (binding && PL_strcmp(binding->mCacheEntry->Key()->get(), key->get()) != 0) {
408 *collision = PR_TRUE;
409 return nsnull;
411 binding = nsnull;
413 // lookup hash number in cache map
414 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
415 if (NS_FAILED(rv)) return nsnull; // XXX log error?
417 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
418 if (!diskEntry) return nsnull;
420 // compare key to be sure
421 if (strcmp(diskEntry->Key(), key->get()) != 0) {
422 *collision = PR_TRUE;
423 return nsnull;
426 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
427 if (!entry) return nsnull;
429 binding = mBindery.CreateBinding(entry, &record);
430 if (!binding) {
431 delete entry;
432 return nsnull;
435 return entry;
440 * NOTE: called while holding the cache service lock
442 nsresult
443 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
445 nsresult rv = NS_OK;
446 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
447 NS_ASSERTION(binding, "DeactivateEntry: binding == nsnull");
448 if (!binding) return NS_ERROR_UNEXPECTED;
450 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
451 entry, binding->mRecord.HashNumber()));
453 if (entry->IsDoomed()) {
454 // delete data, entry, record from disk for entry
455 rv = mCacheMap.DeleteStorage(&binding->mRecord);
457 } else {
458 // save stuff to disk for entry
459 rv = mCacheMap.WriteDiskCacheEntry(binding);
460 if (NS_FAILED(rv)) {
461 // clean up as best we can
462 (void) mCacheMap.DeleteStorage(&binding->mRecord);
463 (void) mCacheMap.DeleteRecord(&binding->mRecord);
464 binding->mDoomed = PR_TRUE; // record is no longer in cache map
468 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
469 delete entry; // which will release binding
470 return rv;
475 * BindEntry()
476 * no hash number collision -> no problem
477 * collision
478 * record not active -> evict, no problem
479 * record is active
480 * record is already doomed -> record shouldn't have been in map, no problem
481 * record is not doomed -> doom, and replace record in map
483 * walk matching hashnumber list to find lowest generation number
484 * take generation number from other (data/meta) location,
485 * or walk active list
487 * NOTE: called while holding the cache service lock
489 nsresult
490 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
492 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
493 nsresult rv = NS_OK;
494 nsDiskCacheRecord record, oldRecord;
496 // create a new record for this entry
497 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
498 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
500 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
501 entry, record.HashNumber()));
503 if (!entry->IsDoomed()) {
504 // if entry isn't doomed, add it to the cache map
505 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
506 if (NS_FAILED(rv)) return rv;
508 PRUint32 oldHashNumber = oldRecord.HashNumber();
509 if (oldHashNumber) {
510 // gotta evict this one first
511 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
512 if (oldBinding) {
513 // XXX if debug : compare keys for hashNumber collision
515 if (!oldBinding->mCacheEntry->IsDoomed()) {
516 // we've got a live one!
517 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
518 // storage will be delete when oldBinding->mCacheEntry is Deactivated
520 } else {
521 // delete storage
522 // XXX if debug : compare keys for hashNumber collision
523 rv = mCacheMap.DeleteStorage(&oldRecord);
524 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
529 // Make sure this entry has its associated nsDiskCacheBinding attached.
530 nsDiskCacheBinding * binding = mBindery.CreateBinding(entry, &record);
531 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
532 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
533 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
535 return NS_OK;
540 * NOTE: called while holding the cache service lock
542 void
543 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
545 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
547 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
548 NS_ASSERTION(binding, "DoomEntry: binding == nsnull");
549 if (!binding) return;
551 if (!binding->mDoomed) {
552 // so it can't be seen by FindEntry() ever again.
553 #ifdef DEBUG
554 nsresult rv =
555 #endif
556 mCacheMap.DeleteRecord(&binding->mRecord);
557 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
558 binding->mDoomed = PR_TRUE; // record in no longer in cache map
564 * NOTE: called while holding the cache service lock
566 nsresult
567 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
568 nsCacheAccessMode mode,
569 PRUint32 offset,
570 nsIInputStream ** result)
572 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
573 entry, mode, offset));
575 NS_ENSURE_ARG_POINTER(entry);
576 NS_ENSURE_ARG_POINTER(result);
578 nsresult rv;
579 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
580 NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
582 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
584 rv = binding->EnsureStreamIO();
585 if (NS_FAILED(rv)) return rv;
587 return binding->mStreamIO->GetInputStream(offset, result);
592 * NOTE: called while holding the cache service lock
594 nsresult
595 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
596 nsCacheAccessMode mode,
597 PRUint32 offset,
598 nsIOutputStream ** result)
600 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
601 entry, mode, offset));
603 NS_ENSURE_ARG_POINTER(entry);
604 NS_ENSURE_ARG_POINTER(result);
606 nsresult rv;
607 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
608 NS_ENSURE_TRUE(binding, NS_ERROR_UNEXPECTED);
610 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
612 rv = binding->EnsureStreamIO();
613 if (NS_FAILED(rv)) return rv;
615 return binding->mStreamIO->GetOutputStream(offset, result);
620 * NOTE: called while holding the cache service lock
622 nsresult
623 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
624 nsIFile ** result)
626 NS_ENSURE_ARG_POINTER(result);
627 *result = nsnull;
629 nsresult rv;
631 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
632 if (!binding) {
633 NS_WARNING("GetFileForEntry: binding == nsnull");
634 return NS_ERROR_UNEXPECTED;
637 // check/set binding->mRecord for separate file, sync w/mCacheMap
638 if (binding->mRecord.DataLocationInitialized()) {
639 if (binding->mRecord.DataFile() != 0)
640 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
642 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
643 } else {
644 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
645 binding->mRecord.SetDataFileSize(0); // 1k minimum
646 if (!binding->mDoomed) {
647 // record stored in cache map, so update it
648 rv = mCacheMap.UpdateRecord(&binding->mRecord);
649 if (NS_FAILED(rv)) return rv;
653 nsCOMPtr<nsIFile> file;
654 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
655 nsDiskCache::kData,
656 getter_AddRefs(file));
657 if (NS_FAILED(rv)) return rv;
659 NS_IF_ADDREF(*result = file);
660 return NS_OK;
665 * This routine will get called every time an open descriptor is written to.
667 * NOTE: called while holding the cache service lock
669 nsresult
670 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
672 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
673 entry, deltaSize));
675 // If passed a negative value, then there's nothing to do.
676 if (deltaSize < 0)
677 return NS_OK;
679 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
680 NS_ASSERTION(binding, "OnDataSizeChange: binding == nsnull");
681 if (!binding) return NS_ERROR_UNEXPECTED;
683 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
685 PRUint32 newSize = entry->DataSize() + deltaSize;
686 PRUint32 newSizeK = ((newSize + 0x3FF) >> 10);
688 // If the new size is larger than max. file size or larger than
689 // half the cache capacity (which is in KiB's), doom the entry and abort
690 if ((newSize > kMaxDataFileSize) || (newSizeK > mCacheCapacity/2)) {
691 #ifdef DEBUG
692 nsresult rv =
693 #endif
694 nsCacheService::DoomEntry(entry);
695 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
696 return NS_ERROR_ABORT;
699 PRUint32 sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
701 NS_ASSERTION(sizeK < USHRT_MAX, "data size out of range");
702 NS_ASSERTION(newSizeK < USHRT_MAX, "data size out of range");
704 // pre-evict entries to make space for new data
705 PRUint32 targetCapacity = mCacheCapacity > (newSizeK - sizeK)
706 ? mCacheCapacity - (newSizeK - sizeK)
707 : 0;
708 EvictDiskCacheEntries(targetCapacity);
710 return NS_OK;
714 /******************************************************************************
715 * EntryInfoVisitor
716 *****************************************************************************/
717 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
719 public:
720 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
721 nsICacheVisitor * visitor)
722 : mCacheMap(cacheMap)
723 , mVisitor(visitor)
726 virtual PRInt32 VisitRecord(nsDiskCacheRecord * mapRecord)
728 // XXX optimization: do we have this record in memory?
730 // read in the entry (metadata)
731 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
732 if (!diskEntry) {
733 return kVisitNextRecord;
736 // create nsICacheEntryInfo
737 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
738 if (!entryInfo) {
739 return kStopVisitingRecords;
741 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
743 PRBool keepGoing;
744 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
745 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
748 private:
749 nsDiskCacheMap * mCacheMap;
750 nsICacheVisitor * mVisitor;
754 nsresult
755 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
757 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
758 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
759 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
761 PRBool keepGoing;
762 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
763 if (NS_FAILED(rv)) return rv;
765 if (keepGoing) {
766 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
767 return mCacheMap.VisitRecords(&infoVisitor);
770 return NS_OK;
774 nsresult
775 nsDiskCacheDevice::EvictEntries(const char * clientID)
777 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
779 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
780 nsresult rv;
782 if (clientID == nsnull) {
783 // we're clearing the entire disk cache
784 rv = ClearDiskCache();
785 if (rv != NS_ERROR_CACHE_IN_USE)
786 return rv;
789 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
790 rv = mCacheMap.VisitRecords(&evictor);
792 if (clientID == nsnull) // we tried to clear the entire cache
793 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
794 return rv;
799 * private methods
802 nsresult
803 nsDiskCacheDevice::OpenDiskCache()
805 // if we don't have a cache directory, create one and open it
806 PRBool exists;
807 nsresult rv = mCacheDirectory->Exists(&exists);
808 if (NS_FAILED(rv))
809 return rv;
811 PRBool trashing = PR_FALSE;
812 if (exists) {
813 // Try opening cache map file.
814 rv = mCacheMap.Open(mCacheDirectory);
815 // move "corrupt" caches to trash
816 if (rv == NS_ERROR_FILE_CORRUPTED) {
817 rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
818 if (NS_FAILED(rv))
819 return rv;
820 exists = PR_FALSE;
821 trashing = PR_TRUE;
823 else if (NS_FAILED(rv))
824 return rv;
827 // if we don't have a cache directory, create one and open it
828 if (!exists) {
829 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
830 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
831 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
832 if (NS_FAILED(rv))
833 return rv;
835 // reopen the cache map
836 rv = mCacheMap.Open(mCacheDirectory);
837 if (NS_FAILED(rv))
838 return rv;
841 if (!trashing) {
842 // delete any trash files leftover from a previous run
843 nsCOMPtr<nsIFile> trashDir;
844 GetTrashDir(mCacheDirectory, &trashDir);
845 if (trashDir) {
846 PRBool exists;
847 if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
848 DeleteDir(trashDir, PR_FALSE, PR_FALSE);
852 return NS_OK;
856 nsresult
857 nsDiskCacheDevice::ClearDiskCache()
859 if (mBindery.ActiveBindings())
860 return NS_ERROR_CACHE_IN_USE;
862 nsresult rv = Shutdown_Private(PR_FALSE); // false: don't bother flushing
863 if (NS_FAILED(rv))
864 return rv;
866 // If the disk cache directory is already gone, then it's not an error if
867 // we fail to delete it ;-)
868 rv = DeleteDir(mCacheDirectory, PR_TRUE, PR_FALSE);
869 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
870 return rv;
872 return Init();
876 nsresult
877 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 targetCapacity)
879 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
880 targetCapacity));
882 NS_ASSERTION(targetCapacity > 0, "oops");
884 if (mCacheMap.TotalSize() < targetCapacity)
885 return NS_OK;
887 // targetCapacity is in KiB's
888 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nsnull);
889 return mCacheMap.EvictRecords(&evictor);
894 * methods for prefs
897 void
898 nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile * parentDir)
900 nsresult rv;
901 PRBool exists;
903 if (Initialized()) {
904 NS_ASSERTION(PR_FALSE, "Cannot switch cache directory when initialized");
905 return;
908 if (!parentDir) {
909 mCacheDirectory = nsnull;
910 return;
913 // ensure parent directory exists
914 rv = parentDir->Exists(&exists);
915 if (NS_SUCCEEDED(rv) && !exists)
916 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
917 if (NS_FAILED(rv)) return;
919 // ensure cache directory exists
920 nsCOMPtr<nsIFile> directory;
922 rv = parentDir->Clone(getter_AddRefs(directory));
923 if (NS_FAILED(rv)) return;
924 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
925 if (NS_FAILED(rv)) return;
927 mCacheDirectory = do_QueryInterface(directory);
931 void
932 nsDiskCacheDevice::getCacheDirectory(nsILocalFile ** result)
934 *result = mCacheDirectory;
935 NS_IF_ADDREF(*result);
940 * NOTE: called while holding the cache service lock
942 void
943 nsDiskCacheDevice::SetCapacity(PRUint32 capacity)
945 // Units are KiB's
946 mCacheCapacity = capacity;
947 if (Initialized()) {
948 // start evicting entries if the new size is smaller!
949 EvictDiskCacheEntries(mCacheCapacity);
954 PRUint32 nsDiskCacheDevice::getCacheCapacity()
956 return mCacheCapacity;
960 PRUint32 nsDiskCacheDevice::getCacheSize()
962 return mCacheMap.TotalSize();
966 PRUint32 nsDiskCacheDevice::getEntryCount()
968 return mCacheMap.EntryCount();