Back to index

lightning-sunbird  0.9+nobinonly
nsCacheService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is nsCacheService.cpp, released
00017  * February 10, 2001.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 2001
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *   Gordon Sheridan, 10-February-2001
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "necko-config.h"
00042 
00043 #include "nsCache.h"
00044 #include "nsCacheService.h"
00045 #include "nsCacheRequest.h"
00046 #include "nsCacheEntry.h"
00047 #include "nsCacheEntryDescriptor.h"
00048 #include "nsCacheDevice.h"
00049 #include "nsMemoryCacheDevice.h"
00050 #include "nsICacheVisitor.h"
00051 
00052 #ifdef NECKO_DISK_CACHE_SQL
00053 #include "nsDiskCacheDeviceSQL.h"
00054 #else
00055 #include "nsDiskCacheDevice.h"
00056 #endif
00057 
00058 #include "nsAutoLock.h"
00059 #include "nsIEventQueue.h"
00060 #include "nsIObserverService.h"
00061 #include "nsIPrefService.h"
00062 #include "nsIPrefBranch.h"
00063 #include "nsIPrefBranch2.h"
00064 #include "nsILocalFile.h"
00065 #include "nsDirectoryServiceDefs.h"
00066 #include "nsAppDirectoryServiceDefs.h"
00067 #include "nsVoidArray.h"
00068 #include "nsDeleteDir.h"
00069 #include <math.h>  // for log()
00070 
00071 
00072 /******************************************************************************
00073  * nsCacheProfilePrefObserver
00074  *****************************************************************************/
00075 #ifdef XP_MAC
00076 #pragma mark nsCacheProfilePrefObserver
00077 #endif
00078 
00079 #define DISK_CACHE_ENABLE_PREF      "browser.cache.disk.enable"
00080 #define DISK_CACHE_DIR_PREF         "browser.cache.disk.parent_directory"
00081 #define DISK_CACHE_CAPACITY_PREF    "browser.cache.disk.capacity"
00082 #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
00083 #define DISK_CACHE_CAPACITY         51200
00084 
00085 #define MEMORY_CACHE_ENABLE_PREF    "browser.cache.memory.enable"
00086 #define MEMORY_CACHE_CAPACITY_PREF  "browser.cache.memory.capacity"
00087 #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
00088 #define MEMORY_CACHE_CAPACITY       4096
00089 
00090 #define BROWSER_CACHE_MEMORY_CAPACITY  4096
00091 
00092 
00093 class nsCacheProfilePrefObserver : public nsIObserver
00094 {
00095 public:
00096     NS_DECL_ISUPPORTS
00097     NS_DECL_NSIOBSERVER
00098 
00099     nsCacheProfilePrefObserver()
00100         : mHaveProfile(PR_FALSE)
00101         , mDiskCacheEnabled(PR_FALSE)
00102         , mDiskCacheCapacity(0)
00103         , mMemoryCacheEnabled(PR_TRUE)
00104         , mMemoryCacheCapacity(-1)
00105     {
00106     }
00107 
00108     virtual ~nsCacheProfilePrefObserver() {}
00109     
00110     nsresult        Install();
00111     nsresult        Remove();
00112     nsresult        ReadPrefs(nsIPrefBranch* branch);
00113     
00114     PRBool          DiskCacheEnabled();
00115     PRInt32         DiskCacheCapacity()         { return mDiskCacheCapacity; }
00116     nsILocalFile *  DiskCacheParentDirectory()  { return mDiskCacheParentDirectory; }
00117     
00118     PRBool          MemoryCacheEnabled();
00119     PRInt32         MemoryCacheCapacity()       { return mMemoryCacheCapacity; }
00120 
00121 private:
00122     PRBool                  mHaveProfile;
00123     
00124     PRBool                  mDiskCacheEnabled;
00125     PRInt32                 mDiskCacheCapacity;
00126     nsCOMPtr<nsILocalFile>  mDiskCacheParentDirectory;
00127     
00128     PRBool                  mMemoryCacheEnabled;
00129     PRInt32                 mMemoryCacheCapacity;
00130 };
00131 
00132 NS_IMPL_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver)
00133 
00134 
00135 nsresult
00136 nsCacheProfilePrefObserver::Install()
00137 {
00138     nsresult rv, rv2 = NS_OK;
00139     
00140     // install profile-change observer
00141     nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
00142     if (NS_FAILED(rv)) return rv;
00143     NS_ENSURE_ARG(observerService);
00144     
00145     rv = observerService->AddObserver(this, "profile-before-change", PR_FALSE);
00146     if (NS_FAILED(rv)) rv2 = rv;
00147     
00148     rv = observerService->AddObserver(this, "profile-after-change", PR_FALSE);
00149     if (NS_FAILED(rv)) rv2 = rv;
00150 
00151 
00152     // install xpcom shutdown observer
00153     rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
00154     if (NS_FAILED(rv)) rv2 = rv;
00155     
00156     
00157     // install preferences observer
00158     nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
00159     if (!branch) return NS_ERROR_FAILURE;
00160 
00161     char * prefList[] = { 
00162         DISK_CACHE_ENABLE_PREF,
00163         DISK_CACHE_CAPACITY_PREF,
00164         DISK_CACHE_DIR_PREF,
00165         MEMORY_CACHE_ENABLE_PREF,
00166         MEMORY_CACHE_CAPACITY_PREF
00167     };
00168     int listCount = NS_ARRAY_LENGTH(prefList);
00169       
00170     for (int i=0; i<listCount; i++) {
00171         rv = branch->AddObserver(prefList[i], this, PR_FALSE);
00172         if (NS_FAILED(rv))  rv2 = rv;
00173     }
00174 
00175     // Determine if we have a profile already
00176     //     Install() is called *after* the profile-after-change notification
00177     //     when there is only a single profile, or it is specified on the
00178     //     commandline at startup.
00179     //     In that case, we detect the presence of a profile by the existence
00180     //     of the NS_APP_USER_PROFILE_50_DIR directory.
00181 
00182     nsCOMPtr<nsIFile>  directory;
00183     rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
00184                                 getter_AddRefs(directory));
00185     if (NS_SUCCEEDED(rv)) {
00186         mHaveProfile = PR_TRUE;
00187     }
00188 
00189     rv = ReadPrefs(branch);
00190     
00191     return NS_SUCCEEDED(rv) ? rv2 : rv;
00192 }
00193 
00194 
00195 nsresult
00196 nsCacheProfilePrefObserver::Remove()
00197 {
00198     nsresult rv, rv2 = NS_OK;
00199 
00200     // remove Observer Service observers
00201     nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
00202     if (NS_FAILED(rv)) return rv;
00203 
00204     rv = observerService->RemoveObserver(this, "profile-before-change");
00205     if (NS_FAILED(rv)) rv2 = rv;
00206     
00207     rv = observerService->RemoveObserver(this, "profile-after-change");
00208     if (NS_FAILED(rv)) rv2 = rv;
00209 
00210     rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
00211     if (NS_FAILED(rv)) rv2 = rv;
00212 
00213 
00214     // remove Pref Service observers
00215     nsCOMPtr<nsIPrefBranch2> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID);
00216 
00217     // remove Disk cache pref observers
00218     rv  = prefInternal->RemoveObserver(DISK_CACHE_ENABLE_PREF, this);
00219     if (NS_FAILED(rv)) rv2 = rv;
00220 
00221     rv  = prefInternal->RemoveObserver(DISK_CACHE_CAPACITY_PREF, this);
00222     if (NS_FAILED(rv)) rv2 = rv;
00223 
00224     rv  = prefInternal->RemoveObserver(DISK_CACHE_DIR_PREF, this);
00225     if (NS_FAILED(rv)) rv2 = rv;
00226     
00227     // remove Memory cache pref observers
00228     rv = prefInternal->RemoveObserver(MEMORY_CACHE_ENABLE_PREF, this);
00229     if (NS_FAILED(rv)) rv2 = rv;
00230 
00231     rv = prefInternal->RemoveObserver(MEMORY_CACHE_CAPACITY_PREF, this);
00232     // if (NS_FAILED(rv)) rv2 = rv;
00233 
00234     return NS_SUCCEEDED(rv) ? rv2 : rv;
00235 }
00236 
00237 
00238 NS_IMETHODIMP
00239 nsCacheProfilePrefObserver::Observe(nsISupports *     subject,
00240                                     const char *      topic,
00241                                     const PRUnichar * data_unicode)
00242 {
00243     nsresult rv;
00244     NS_ConvertUCS2toUTF8 data(data_unicode);
00245     CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
00246 
00247     if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
00248         // xpcom going away, shutdown cache service
00249         if (nsCacheService::GlobalInstance())
00250             nsCacheService::GlobalInstance()->Shutdown();
00251     
00252     } else if (!strcmp("profile-before-change", topic)) {
00253         // profile before change
00254         mHaveProfile = PR_FALSE;
00255 
00256         // XXX shutdown devices
00257         nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
00258                                                   data.get()));
00259         
00260     } else if (!strcmp("profile-after-change", topic)) {
00261         // profile after change
00262         mHaveProfile = PR_TRUE;
00263         nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
00264         ReadPrefs(branch);
00265         nsCacheService::OnProfileChanged();
00266     
00267     } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
00268 
00269         // ignore pref changes until we're done switch profiles
00270         if (!mHaveProfile)  return NS_OK;
00271 
00272         nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
00273         if (NS_FAILED(rv))  return rv;
00274 
00275 #ifdef NECKO_DISK_CACHE
00276         // which preference changed?
00277         if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
00278 
00279             rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
00280                                      &mDiskCacheEnabled);
00281             if (NS_FAILED(rv))  return rv;
00282             nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
00283 
00284         } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
00285 
00286             PRInt32 capacity = 0;
00287             rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
00288             if (NS_FAILED(rv))  return rv;
00289             mDiskCacheCapacity = PR_MAX(0, capacity);
00290             nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
00291 #if 0            
00292         } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
00293             // XXX We probaby don't want to respond to this pref except after
00294             // XXX profile changes.  Ideally, there should be somekind of user
00295             // XXX notification that the pref change won't take effect until
00296             // XXX the next time the profile changes (browser launch)
00297 #endif            
00298         } else 
00299 #endif // !NECKO_DISK_CACHE
00300         if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
00301 
00302             rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
00303                                      &mMemoryCacheEnabled);
00304             if (NS_FAILED(rv))  return rv;
00305             nsCacheService::SetMemoryCacheEnabled(MemoryCacheEnabled());
00306             
00307         } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
00308 
00309             (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
00310                                       &mMemoryCacheCapacity);
00311             nsCacheService::SetMemoryCacheCapacity(mMemoryCacheCapacity);
00312         }
00313     }
00314     
00315     return NS_OK;
00316 }
00317 
00318 
00319 nsresult
00320 nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
00321 {
00322     nsresult rv = NS_OK;
00323 
00324 #ifdef NECKO_DISK_CACHE
00325     // read disk cache device prefs
00326     mDiskCacheEnabled = PR_TRUE;  // presume disk cache is enabled
00327     (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
00328 
00329     mDiskCacheCapacity = DISK_CACHE_CAPACITY;
00330     (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
00331     mDiskCacheCapacity = PR_MAX(0, mDiskCacheCapacity);
00332 
00333     (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF,     // ignore error
00334                                    NS_GET_IID(nsILocalFile),
00335                                    getter_AddRefs(mDiskCacheParentDirectory));
00336     
00337     if (!mDiskCacheParentDirectory) {
00338         nsCOMPtr<nsIFile>  directory;
00339 
00340         // try to get the disk cache parent directory
00341         rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
00342                                     getter_AddRefs(directory));
00343         if (NS_FAILED(rv)) {
00344             // try to get the profile directory (there may not be a profile yet)
00345             nsCOMPtr<nsIFile> profDir;
00346             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
00347                                    getter_AddRefs(profDir));
00348             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
00349                                    getter_AddRefs(directory));
00350             if (!directory)
00351                 directory = profDir;
00352             else if (profDir) {
00353                 PRBool same;
00354                 if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
00355                     // We no longer store the cache directory in the main
00356                     // profile directory, so we should cleanup the old one.
00357                     rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
00358                     if (NS_SUCCEEDED(rv)) {
00359                         PRBool exists;
00360                         if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
00361                             DeleteDir(profDir, PR_FALSE, PR_FALSE);
00362                     }
00363                 }
00364             }
00365         }
00366 #if DEBUG
00367         if (!directory) {
00368             // use current process directory during development
00369             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
00370                                         getter_AddRefs(directory));
00371         }
00372 #endif
00373         if (directory)
00374             mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
00375     }
00376 #endif // !NECKO_DISK_CACHE
00377     
00378     // read memory cache device prefs
00379     (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
00380 
00381     (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
00382                               &mMemoryCacheCapacity);
00383         
00384     return rv;
00385 }
00386 
00387 
00388 PRBool
00389 nsCacheProfilePrefObserver::DiskCacheEnabled()
00390 {
00391     if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory))  return PR_FALSE;
00392     return mDiskCacheEnabled;
00393 }
00394 
00395     
00396 PRBool
00397 nsCacheProfilePrefObserver::MemoryCacheEnabled()
00398 {
00399     if (mMemoryCacheCapacity == 0)  return PR_FALSE;
00400     return mMemoryCacheEnabled;
00401 }
00402 
00403 
00404 
00405 /******************************************************************************
00406  * nsCacheService
00407  *****************************************************************************/
00408 #ifdef XP_MAC
00409 #pragma mark -
00410 #pragma mark nsCacheService
00411 #endif
00412 
00413 nsCacheService *   nsCacheService::gService = nsnull;
00414 
00415 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService)
00416 
00417 nsCacheService::nsCacheService()
00418     : mCacheServiceLock(nsnull),
00419       mInitialized(PR_FALSE),
00420       mEnableMemoryDevice(PR_TRUE),
00421       mEnableDiskDevice(PR_TRUE),
00422       mMemoryDevice(nsnull),
00423       mDiskDevice(nsnull),
00424       mTotalEntries(0),
00425       mCacheHits(0),
00426       mCacheMisses(0),
00427       mMaxKeyLength(0),
00428       mMaxDataSize(0),
00429       mMaxMetaSize(0),
00430       mDeactivateFailures(0),
00431       mDeactivatedUnboundEntries(0)
00432 {
00433     NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!");
00434     gService = this;
00435 
00436     // create list of cache devices
00437     PR_INIT_CLIST(&mDoomedEntries);
00438   
00439     // allocate service lock
00440     mCacheServiceLock = PR_NewLock();
00441 }
00442 
00443 nsCacheService::~nsCacheService()
00444 {
00445     if (mInitialized) // Shutdown hasn't been called yet.
00446         (void) Shutdown();
00447 
00448     PR_DestroyLock(mCacheServiceLock);
00449     gService = nsnull;
00450 }
00451 
00452 
00453 nsresult
00454 nsCacheService::Init()
00455 {
00456     NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
00457     if (mInitialized)
00458         return NS_ERROR_ALREADY_INITIALIZED;
00459 
00460     if (mCacheServiceLock == nsnull)
00461         return NS_ERROR_OUT_OF_MEMORY;
00462 
00463     CACHE_LOG_INIT();
00464 
00465     // initialize hashtable for active cache entries
00466     nsresult rv = mActiveEntries.Init();
00467     if (NS_FAILED(rv)) return rv;
00468     
00469     // get references to services we'll be using frequently
00470     mEventQService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00471     if (NS_FAILED(rv)) return rv;
00472     
00473     mProxyObjectManager = do_GetService(NS_XPCOMPROXY_CONTRACTID, &rv);
00474     if (NS_FAILED(rv)) return rv;
00475 
00476     // create profile/preference observer
00477     mObserver = new nsCacheProfilePrefObserver();
00478     if (!mObserver)  return NS_ERROR_OUT_OF_MEMORY;
00479     NS_ADDREF(mObserver);
00480     
00481     mObserver->Install();
00482     mEnableDiskDevice =   mObserver->DiskCacheEnabled();
00483     mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
00484 
00485     rv = CreateMemoryDevice();
00486     if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_AVAILABLE))
00487         return rv;
00488     
00489     mInitialized = PR_TRUE;
00490     return NS_OK;
00491 }
00492 
00493 
00494 void
00495 nsCacheService::Shutdown()
00496 {
00497     nsAutoLock  lock(mCacheServiceLock);
00498     NS_ASSERTION(mInitialized, 
00499                  "can't shutdown nsCacheService unless it has been initialized.");
00500 
00501     if (mInitialized) {
00502 
00503         mInitialized = PR_FALSE;
00504 
00505         mObserver->Remove();
00506         NS_RELEASE(mObserver);
00507         
00508         // Clear entries
00509         ClearDoomList();
00510         ClearActiveEntries();
00511 
00512         // deallocate memory and disk caches
00513         delete mMemoryDevice;
00514         mMemoryDevice = nsnull;
00515 
00516 #ifdef NECKO_DISK_CACHE
00517         delete mDiskDevice;
00518         mDiskDevice = nsnull;
00519 
00520 #if defined(PR_LOGGING)
00521         LogCacheStatistics();
00522 #endif
00523 #endif
00524     }
00525 }
00526 
00527 
00528 NS_METHOD
00529 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
00530 {
00531     nsresult  rv;
00532 
00533     if (aOuter != nsnull)
00534         return NS_ERROR_NO_AGGREGATION;
00535 
00536     nsCacheService * cacheService = new nsCacheService();
00537     if (cacheService == nsnull)
00538         return NS_ERROR_OUT_OF_MEMORY;
00539 
00540     NS_ADDREF(cacheService);
00541     rv = cacheService->Init();
00542     if (NS_SUCCEEDED(rv)) {
00543         rv = cacheService->QueryInterface(aIID, aResult);
00544     }
00545     NS_RELEASE(cacheService);
00546     return rv;
00547 }
00548 
00549 
00550 NS_IMETHODIMP
00551 nsCacheService::CreateSession(const char *          clientID,
00552                               nsCacheStoragePolicy  storagePolicy, 
00553                               PRBool                streamBased,
00554                               nsICacheSession     **result)
00555 {
00556     *result = nsnull;
00557 
00558     if (this == nsnull)  return NS_ERROR_NOT_AVAILABLE;
00559 
00560     nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
00561     if (!session)  return NS_ERROR_OUT_OF_MEMORY;
00562 
00563     NS_ADDREF(*result = session);
00564 
00565     return NS_OK;
00566 }
00567 
00568 
00569 nsresult
00570 nsCacheService::EvictEntriesForSession(nsCacheSession * session)
00571 {
00572     NS_ASSERTION(gService, "nsCacheService::gService is null.");
00573     return gService->EvictEntriesForClient(session->ClientID()->get(),
00574                                  session->StoragePolicy());
00575 }
00576 
00577 
00578 nsresult
00579 nsCacheService::EvictEntriesForClient(const char *          clientID,
00580                                       nsCacheStoragePolicy  storagePolicy)
00581 {
00582     if (this == nsnull) return NS_ERROR_NOT_AVAILABLE; // XXX eh?
00583 
00584     nsCOMPtr<nsIObserverService> obsSvc =
00585         do_GetService("@mozilla.org/observer-service;1");
00586     if (obsSvc) {
00587         // Proxy to the UI thread since the observer service isn't thredsafe.
00588         // We use an async proxy, since this it's not important whether this
00589         // notification happens before or after the actual eviction.
00590 
00591         nsCOMPtr<nsIObserverService> obsProxy;
00592         NS_GetProxyForObject(NS_UI_THREAD_EVENTQ,
00593                              NS_GET_IID(nsIObserverService),
00594                              obsSvc, PROXY_ASYNC, getter_AddRefs(obsProxy));
00595 
00596         if (obsProxy) {
00597             obsProxy->NotifyObservers(this,
00598                                       NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
00599                                       nsnull);
00600         }
00601     }
00602 
00603     nsAutoLock lock(mCacheServiceLock);
00604     nsresult rv = NS_OK;
00605 
00606 #ifdef NECKO_DISK_CACHE
00607     if (storagePolicy == nsICache::STORE_ANYWHERE ||
00608         storagePolicy == nsICache::STORE_ON_DISK) {
00609 
00610         if (mEnableDiskDevice) {
00611             if (!mDiskDevice) {
00612                 rv = CreateDiskDevice();
00613                 if (NS_FAILED(rv)) return rv;
00614             }
00615             rv = mDiskDevice->EvictEntries(clientID);
00616             if (NS_FAILED(rv)) return rv;
00617         }
00618     }
00619 #endif // !NECKO_DISK_CACHE
00620 
00621     if (storagePolicy == nsICache::STORE_ANYWHERE ||
00622         storagePolicy == nsICache::STORE_IN_MEMORY) {
00623 
00624         // If there is no memory device, there is no need to evict it...
00625         if (mMemoryDevice) {
00626             rv = mMemoryDevice->EvictEntries(clientID);
00627             if (NS_FAILED(rv)) return rv;
00628         }
00629     }
00630 
00631     return NS_OK;
00632 }
00633 
00634 
00635 nsresult        
00636 nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy  storagePolicy,
00637                                           PRBool *              result)
00638 {
00639     if (gService == nsnull) return NS_ERROR_NOT_AVAILABLE;
00640     nsAutoLock lock(gService->mCacheServiceLock);
00641 
00642     *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
00643     return NS_OK;
00644 }
00645 
00646 
00647 PRBool        
00648 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy  storagePolicy)
00649 {
00650     if (gService->mEnableMemoryDevice &&
00651         (storagePolicy == nsICache::STORE_ANYWHERE ||
00652          storagePolicy == nsICache::STORE_IN_MEMORY)) {
00653         return PR_TRUE;
00654     }
00655     if (gService->mEnableDiskDevice &&
00656         (storagePolicy == nsICache::STORE_ANYWHERE ||
00657          storagePolicy == nsICache::STORE_ON_DISK  ||
00658          storagePolicy == nsICache::STORE_ON_DISK_AS_FILE)) {
00659         return PR_TRUE;
00660     }
00661     
00662     return PR_FALSE;
00663 }
00664 
00665 
00666 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
00667 {
00668     NS_ENSURE_ARG_POINTER(visitor);
00669 
00670     nsAutoLock lock(mCacheServiceLock);
00671 
00672     if (!(mEnableDiskDevice || mEnableMemoryDevice))
00673         return NS_ERROR_NOT_AVAILABLE;
00674 
00675     // XXX record the fact that a visitation is in progress, 
00676     // XXX i.e. keep list of visitors in progress.
00677     
00678     nsresult rv = NS_OK;
00679     // If there is no memory device, there are then also no entries to visit...
00680     if (mMemoryDevice) {
00681         rv = mMemoryDevice->Visit(visitor);
00682         if (NS_FAILED(rv)) return rv;
00683     }
00684 
00685 #ifdef NECKO_DISK_CACHE
00686     if (mEnableDiskDevice) {
00687         if (!mDiskDevice) {
00688             rv = CreateDiskDevice();
00689             if (NS_FAILED(rv)) return rv;
00690         }
00691         rv = mDiskDevice->Visit(visitor);
00692         if (NS_FAILED(rv)) return rv;
00693     }
00694 #endif // !NECKO_DISK_CACHE
00695 
00696     // XXX notify any shutdown process that visitation is complete for THIS visitor.
00697     // XXX keep queue of visitors
00698 
00699     return NS_OK;
00700 }
00701 
00702 
00703 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
00704 {
00705     return  EvictEntriesForClient(nsnull, storagePolicy);
00706 }
00707 
00708 
00712 nsresult
00713 nsCacheService::CreateDiskDevice()
00714 {
00715 #ifdef NECKO_DISK_CACHE
00716     if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
00717     if (mDiskDevice)        return NS_OK;
00718 
00719     mDiskDevice = new nsDiskCacheDevice;
00720     if (!mDiskDevice)       return NS_ERROR_OUT_OF_MEMORY;
00721 
00722     // set the preferences
00723     mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
00724     mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
00725     
00726     nsresult rv = mDiskDevice->Init();
00727     if (NS_FAILED(rv)) {
00728 #if DEBUG
00729         printf("###\n");
00730         printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv);
00731         printf("###    - disabling disk cache for this session.\n");
00732         printf("###\n");
00733 #endif        
00734         mEnableDiskDevice = PR_FALSE;
00735         delete mDiskDevice;
00736         mDiskDevice = nsnull;
00737     }
00738     return rv;
00739 #else // !NECKO_DISK_CACHE
00740     NS_NOTREACHED("nsCacheService::CreateDiskDevice");
00741     return NS_ERROR_NOT_IMPLEMENTED;
00742 #endif
00743 }
00744 
00745 
00746 nsresult
00747 nsCacheService::CreateMemoryDevice()
00748 {
00749     if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
00750     if (mMemoryDevice)        return NS_OK;
00751 
00752     mMemoryDevice = new nsMemoryCacheDevice;
00753     if (!mMemoryDevice)       return NS_ERROR_OUT_OF_MEMORY;
00754     
00755     // set preference
00756     mMemoryDevice->SetCapacity(CacheMemoryAvailable());
00757 
00758     nsresult rv = mMemoryDevice->Init();
00759     if (NS_FAILED(rv)) {
00760         NS_WARNING("Initialization of Memory Cache failed.");
00761         delete mMemoryDevice;
00762         mMemoryDevice = nsnull;
00763     }
00764     return rv;
00765 }
00766 
00767 
00768 nsresult
00769 nsCacheService::CreateRequest(nsCacheSession *   session,
00770                               const nsACString & clientKey,
00771                               nsCacheAccessMode  accessRequested,
00772                               PRBool             blockingMode,
00773                               nsICacheListener * listener,
00774                               nsCacheRequest **  request)
00775 {
00776     NS_ASSERTION(request, "CreateRequest: request is null");
00777      
00778     nsCString * key = new nsCString(*session->ClientID());
00779     if (!key)
00780         return NS_ERROR_OUT_OF_MEMORY;
00781     key->Append(':');
00782     key->Append(clientKey);
00783 
00784     if (mMaxKeyLength < key->Length()) mMaxKeyLength = key->Length();
00785 
00786     // create request
00787     *request = new  nsCacheRequest(key, listener, accessRequested, blockingMode, session);    
00788     if (!*request) {
00789         delete key;
00790         return NS_ERROR_OUT_OF_MEMORY;
00791     }
00792 
00793     if (!listener)  return NS_OK;  // we're sync, we're done.
00794 
00795     // get the nsIEventQueue for the request's thread
00796     (*request)->mThread = PR_GetCurrentThread();
00797     
00798     return NS_OK;
00799 }
00800 
00801 
00802 nsresult
00803 nsCacheService::NotifyListener(nsCacheRequest *          request,
00804                                nsICacheEntryDescriptor * descriptor,
00805                                nsCacheAccessMode         accessGranted,
00806                                nsresult                  error)
00807 {
00808     nsresult rv;
00809 
00810     nsCOMPtr<nsICacheListener> listenerProxy;
00811     NS_ASSERTION(request->mThread, "no thread set in async request!");
00812     nsCOMPtr<nsIEventQueue> eventQ;
00813     mEventQService->GetThreadEventQueue(request->mThread,
00814                                         getter_AddRefs(eventQ));
00815     rv = mProxyObjectManager->GetProxyForObject(eventQ,
00816                                                 NS_GET_IID(nsICacheListener),
00817                                                 request->mListener,
00818                                                 PROXY_ASYNC|PROXY_ALWAYS,
00819                                                 getter_AddRefs(listenerProxy));
00820     if (NS_FAILED(rv)) return rv;
00821 
00822     return listenerProxy->OnCacheEntryAvailable(descriptor, accessGranted, error);
00823 }
00824 
00825 
00826 nsresult
00827 nsCacheService::ProcessRequest(nsCacheRequest *           request,
00828                                PRBool                     calledFromOpenCacheEntry,
00829                                nsICacheEntryDescriptor ** result)
00830 {
00831     // !!! must be called with mCacheServiceLock held !!!
00832     nsresult           rv;
00833     nsCacheEntry *     entry = nsnull;
00834     nsCacheAccessMode  accessGranted = nsICache::ACCESS_NONE;
00835     if (result) *result = nsnull;
00836 
00837     while(1) {  // Activate entry loop
00838         rv = ActivateEntry(request, &entry);  // get the entry for this request
00839         if (NS_FAILED(rv))  break;
00840 
00841         while(1) { // Request Access loop
00842             NS_ASSERTION(entry, "no entry in Request Access loop!");
00843             // entry->RequestAccess queues request on entry
00844             rv = entry->RequestAccess(request, &accessGranted);
00845             if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
00846             
00847             if (request->mListener) // async exits - validate, doom, or close will resume
00848                 return rv;
00849             
00850             if (request->IsBlocking()) {
00851                 PR_Unlock(mCacheServiceLock);
00852                 rv = request->WaitForValidation();
00853                 PR_Lock(mCacheServiceLock);
00854             }
00855 
00856             PR_REMOVE_AND_INIT_LINK(request);
00857             if (NS_FAILED(rv)) break;   // non-blocking mode returns WAIT_FOR_VALIDATION error
00858             // okay, we're ready to process this request, request access again
00859         }
00860         if (rv != NS_ERROR_CACHE_ENTRY_DOOMED)  break;
00861 
00862         if (entry->IsNotInUse()) {
00863             // this request was the last one keeping it around, so get rid of it
00864             DeactivateEntry(entry);
00865         }
00866         // loop back around to look for another entry
00867     }
00868 
00869     nsCOMPtr<nsICacheEntryDescriptor> descriptor;
00870     
00871     if (NS_SUCCEEDED(rv))
00872         rv = entry->CreateDescriptor(request, accessGranted, getter_AddRefs(descriptor));
00873 
00874     if (request->mListener) {  // Asynchronous
00875     
00876         if (NS_FAILED(rv) && calledFromOpenCacheEntry)
00877             return rv;  // skip notifying listener, just return rv to caller
00878             
00879         // call listener to report error or descriptor
00880         nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
00881         if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
00882             rv = rv2;  // trigger delete request
00883         }
00884     } else {        // Synchronous
00885         NS_IF_ADDREF(*result = descriptor);
00886     }
00887     return rv;
00888 }
00889 
00890 
00891 nsresult
00892 nsCacheService::OpenCacheEntry(nsCacheSession *           session,
00893                                const nsACString &         key,
00894                                nsCacheAccessMode          accessRequested,
00895                                PRBool                     blockingMode,
00896                                nsICacheListener *         listener,
00897                                nsICacheEntryDescriptor ** result)
00898 {
00899     NS_ASSERTION(gService, "nsCacheService::gService is null.");
00900     if (result)
00901         *result = nsnull;
00902 
00903     nsCacheRequest * request = nsnull;
00904 
00905     nsAutoLock lock(gService->mCacheServiceLock);
00906     nsresult rv = gService->CreateRequest(session,
00907                                           key,
00908                                           accessRequested,
00909                                           blockingMode,
00910                                           listener,
00911                                           &request);
00912     if (NS_FAILED(rv))  return rv;
00913 
00914     rv = gService->ProcessRequest(request, PR_TRUE, result);
00915 
00916     // delete requests that have completed
00917     if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
00918         delete request;
00919 
00920     return rv;
00921 }
00922 
00923 
00924 nsresult
00925 nsCacheService::ActivateEntry(nsCacheRequest * request, 
00926                               nsCacheEntry ** result)
00927 {
00928     nsresult        rv = NS_OK;
00929 
00930     NS_ASSERTION(request != nsnull, "ActivateEntry called with no request");
00931     if (result) *result = nsnull;
00932     if ((!request) || (!result))  return NS_ERROR_NULL_POINTER;
00933 
00934     // check if the request can be satisfied
00935     if (!mEnableMemoryDevice && !request->IsStreamBased())
00936         return NS_ERROR_FAILURE;
00937     if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
00938         return NS_ERROR_FAILURE;
00939 
00940     // search active entries (including those not bound to device)
00941     nsCacheEntry *entry = mActiveEntries.GetEntry(request->mKey);
00942 
00943     if (!entry) {
00944         // search cache devices for entry
00945         PRBool collision = PR_FALSE;
00946         entry = SearchCacheDevices(request->mKey, request->StoragePolicy(), &collision);
00947         // When there is a hashkey collision just refuse to cache it...
00948         if (collision) return NS_ERROR_CACHE_IN_USE;
00949 
00950         if (entry)  entry->MarkInitialized();
00951     }
00952 
00953     if (entry) {
00954         ++mCacheHits;
00955         entry->Fetched();
00956     } else {
00957         ++mCacheMisses;
00958     }
00959 
00960     if (entry &&
00961         ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
00962          (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
00963           request->WillDoomEntriesIfExpired())))
00964     {
00965         // this is FORCE-WRITE request or the entry has expired
00966         rv = DoomEntry_Internal(entry);
00967         if (NS_FAILED(rv)) {
00968             // XXX what to do?  Increment FailedDooms counter?
00969         }
00970         entry = nsnull;
00971     }
00972 
00973     if (!entry) {
00974         if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
00975             // this is a READ-ONLY request
00976             rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
00977             goto error;
00978         }
00979 
00980         entry = new nsCacheEntry(request->mKey,
00981                                  request->IsStreamBased(),
00982                                  request->StoragePolicy());
00983         if (!entry)
00984             return NS_ERROR_OUT_OF_MEMORY;
00985         
00986         entry->Fetched();
00987         ++mTotalEntries;
00988         
00989         // XXX  we could perform an early bind in some cases based on storage policy
00990     }
00991 
00992     if (!entry->IsActive()) {
00993         rv = mActiveEntries.AddEntry(entry);
00994         if (NS_FAILED(rv)) goto error;
00995         entry->MarkActive();  // mark entry active, because it's now in mActiveEntries
00996     }
00997     *result = entry;
00998     return NS_OK;
00999     
01000  error:
01001     *result = nsnull;
01002     if (entry) {
01003         delete entry;
01004     }
01005     return rv;
01006 }
01007 
01008 
01009 nsCacheEntry *
01010 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, PRBool *collision)
01011 {
01012     nsCacheEntry * entry = nsnull;
01013 
01014     *collision = PR_FALSE;
01015     if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
01016         // If there is no memory device, then there is nothing to search...
01017         if (mMemoryDevice)
01018             entry = mMemoryDevice->FindEntry(key, collision);
01019     }
01020 
01021     if (!entry && 
01022         ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
01023 
01024 #ifdef NECKO_DISK_CACHE
01025         if (mEnableDiskDevice) {
01026             if (!mDiskDevice) {
01027                 nsresult rv = CreateDiskDevice();
01028                 if (NS_FAILED(rv))
01029                     return nsnull;
01030             }
01031             
01032             entry = mDiskDevice->FindEntry(key, collision);
01033         }
01034 #endif // !NECKO_DISK_CACHE
01035     }
01036 
01037     return entry;
01038 }
01039 
01040 
01041 nsCacheDevice *
01042 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
01043 {
01044     nsCacheDevice * device = entry->CacheDevice();
01045     if (device)  return device;
01046 
01047 #ifdef NECKO_DISK_CACHE
01048     if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
01049         // this is the default
01050         if (!mDiskDevice) {
01051             (void)CreateDiskDevice();  // ignore the error (check for mDiskDevice instead)
01052         }
01053 
01054         if (mDiskDevice) {
01055             entry->MarkBinding();  // enter state of binding
01056             nsresult rv = mDiskDevice->BindEntry(entry);
01057             entry->ClearBinding(); // exit state of binding
01058             if (NS_SUCCEEDED(rv))
01059                 device = mDiskDevice;
01060         }
01061     }
01062 #endif // !NECKO_DISK_CACHE
01063      
01064     // if we can't use mDiskDevice, try mMemoryDevice
01065     if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {        
01066         if (!mMemoryDevice) {
01067             (void)CreateMemoryDevice();  // ignore the error (check for mMemoryDevice instead)
01068         }
01069         if (mMemoryDevice) {
01070             entry->MarkBinding();  // enter state of binding
01071             nsresult rv = mMemoryDevice->BindEntry(entry);
01072             entry->ClearBinding(); // exit state of binding
01073             if (NS_SUCCEEDED(rv))
01074                 device = mMemoryDevice;
01075         }
01076     }
01077 
01078     if (device) 
01079         entry->SetCacheDevice(device);
01080     return device;
01081 }
01082 
01083 
01084 nsresult
01085 nsCacheService::DoomEntry(nsCacheEntry * entry)
01086 {
01087     return gService->DoomEntry_Internal(entry);
01088 }
01089 
01090 
01091 nsresult
01092 nsCacheService::DoomEntry_Internal(nsCacheEntry * entry)
01093 {
01094     if (entry->IsDoomed())  return NS_OK;
01095     
01096     nsresult  rv = NS_OK;
01097     entry->MarkDoomed();
01098     
01099     NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
01100     nsCacheDevice * device = entry->CacheDevice();
01101     if (device)  device->DoomEntry(entry);
01102 
01103     if (entry->IsActive()) {
01104         // remove from active entries
01105         mActiveEntries.RemoveEntry(entry);
01106         entry->MarkInactive();
01107      }
01108 
01109     // put on doom list to wait for descriptors to close
01110     NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
01111     PR_APPEND_LINK(entry, &mDoomedEntries);
01112 
01113     // tell pending requests to get on with their lives...
01114     rv = ProcessPendingRequests(entry);
01115     
01116     // All requests have been removed, but there may still be open descriptors
01117     if (entry->IsNotInUse()) {
01118         DeactivateEntry(entry); // tell device to get rid of it
01119     }
01120     return rv;
01121 }
01122 
01123 
01124 static void* PR_CALLBACK
01125 EventHandler(PLEvent *self)
01126 {
01127     nsISupports * object = (nsISupports *)PL_GetEventOwner(self);
01128     NS_RELEASE(object);
01129     return 0;
01130 }
01131 
01132 
01133 static void PR_CALLBACK
01134 DestroyHandler(PLEvent *self)
01135 {
01136     delete self;
01137 }
01138 
01139 
01140 void
01141 nsCacheService::ProxyObjectRelease(nsISupports * object, PRThread * thread)
01142 {
01143     NS_ASSERTION(gService, "nsCacheService not initialized");
01144     NS_ASSERTION(thread, "no thread");
01145     // XXX if thread == current thread, we could avoid posting an event,
01146     // XXX by add this object to a queue and release it when the cache service is unlocked.
01147     
01148     nsCOMPtr<nsIEventQueue> eventQ;
01149     gService->mEventQService->GetThreadEventQueue(thread, getter_AddRefs(eventQ));
01150     NS_ASSERTION(eventQ, "no event queue for thread");
01151     if (!eventQ)  return;
01152     
01153     PLEvent * event = new PLEvent;
01154     if (!event) {
01155         NS_WARNING("failed to allocate a PLEvent.");
01156         return;
01157     }
01158     PL_InitEvent(event, object, EventHandler, DestroyHandler);
01159     eventQ->PostEvent(event);
01160 }
01161 
01162 
01163 void
01164 nsCacheService::OnProfileShutdown(PRBool cleanse)
01165 {
01166     if (!gService)  return;
01167     nsAutoLock lock(gService->mCacheServiceLock);
01168 
01169     gService->DoomActiveEntries();
01170     gService->ClearDoomList();
01171 
01172 #ifdef NECKO_DISK_CACHE
01173     if (gService->mDiskDevice && gService->mEnableDiskDevice) {
01174         if (cleanse)
01175             gService->mDiskDevice->EvictEntries(nsnull);
01176 
01177         gService->mDiskDevice->Shutdown();
01178         gService->mEnableDiskDevice = PR_FALSE;
01179     }
01180 #endif // !NECKO_DISK_CACHE
01181 
01182     if (gService->mMemoryDevice) {
01183         // clear memory cache
01184         gService->mMemoryDevice->EvictEntries(nsnull);
01185     }
01186 
01187 }
01188 
01189 
01190 void
01191 nsCacheService::OnProfileChanged()
01192 {
01193     if (!gService)  return;
01194  
01195     nsresult   rv = NS_OK;
01196     nsAutoLock lock(gService->mCacheServiceLock);
01197     
01198     gService->mEnableDiskDevice   = gService->mObserver->DiskCacheEnabled();
01199     gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
01200     
01201     if (gService->mEnableMemoryDevice && !gService->mMemoryDevice) {
01202         (void) gService->CreateMemoryDevice();
01203     }
01204 
01205 #ifdef NECKO_DISK_CACHE
01206     if (gService->mDiskDevice) {
01207         gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
01208         gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
01209 
01210         // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
01211         rv = gService->mDiskDevice->Init();
01212         if (NS_FAILED(rv)) {
01213             NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
01214             gService->mEnableDiskDevice = PR_FALSE;
01215             // XXX delete mDiskDevice?
01216         }
01217     }
01218 #endif // !NECKO_DISK_CACHE
01219     
01220     if (gService->mMemoryDevice) {
01221         gService->mMemoryDevice->SetCapacity(gService->CacheMemoryAvailable());
01222         rv = gService->mMemoryDevice->Init();
01223         if (NS_FAILED(rv) && (rv != NS_ERROR_ALREADY_INITIALIZED)) {
01224             NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing memory device failed");
01225             gService->mEnableMemoryDevice = PR_FALSE;
01226             // XXX delete mMemoryDevice?
01227         }
01228     }
01229 }
01230 
01231 
01232 void
01233 nsCacheService::SetDiskCacheEnabled(PRBool  enabled)
01234 {
01235     if (!gService)  return;
01236     nsAutoLock lock(gService->mCacheServiceLock);
01237     gService->mEnableDiskDevice = enabled;
01238 }
01239 
01240 
01241 void
01242 nsCacheService::SetDiskCacheCapacity(PRInt32  capacity)
01243 {
01244     if (!gService)  return;
01245     nsAutoLock lock(gService->mCacheServiceLock);
01246 
01247 #ifdef NECKO_DISK_CACHE
01248     if (gService->mDiskDevice) {
01249         gService->mDiskDevice->SetCapacity(capacity);
01250     }
01251 #endif // !NECKO_DISK_CACHE
01252     
01253     gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
01254 }
01255 
01256 
01257 void
01258 nsCacheService::SetMemoryCacheEnabled(PRBool  enabled)
01259 {
01260     if (!gService)  return;
01261     nsAutoLock lock(gService->mCacheServiceLock);
01262     gService->mEnableMemoryDevice = enabled;
01263 
01264     if (enabled) {
01265         if (!gService->mMemoryDevice) {
01266             // allocate memory device, if necessary
01267             (void) gService->CreateMemoryDevice();
01268         }
01269     } else {
01270         if (gService->mMemoryDevice) {
01271             // tell memory device to evict everything
01272             gService->mMemoryDevice->SetCapacity(0);
01273         }
01274     }
01275 }
01276 
01277 
01278 void
01279 nsCacheService::SetMemoryCacheCapacity(PRInt32  capacity)
01280 {
01281     if (!gService)  return;
01282     nsAutoLock lock(gService->mCacheServiceLock);
01283     
01284     gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
01285     if (gService->mEnableMemoryDevice && !gService->mMemoryDevice) {
01286         (void) gService->CreateMemoryDevice();
01287     }
01288 
01289     if (gService->mMemoryDevice) {
01290         gService->mMemoryDevice->SetCapacity(gService->CacheMemoryAvailable());
01291     }
01292 }
01293 
01324 PRInt32
01325 nsCacheService::CacheMemoryAvailable()
01326 {
01327     PRInt32 capacity = mObserver->MemoryCacheCapacity();
01328     if (capacity >= 0)
01329         return capacity;
01330 
01331     PRUint64 bytes = PR_GetPhysicalMemorySize();
01332 
01333     if (LL_CMP(bytes, ==, LL_ZERO))
01334         return 0;
01335 
01336     // Conversion from unsigned int64 to double doesn't work on all platforms.
01337     // We need to truncate the value at LL_MAXINT to make sure we don't
01338     // overflow.
01339     if (LL_CMP(bytes, >, LL_MAXINT))
01340         bytes = LL_MAXINT;
01341 
01342     PRUint64 kbytes;
01343     LL_SHR(kbytes, bytes, 10);
01344 
01345     double kBytesD;
01346     LL_L2D(kBytesD, (PRInt64) kbytes);
01347 
01348     double x = log(kBytesD)/log(2.0) - 14;
01349     if (x > 0) {
01350         capacity = (PRInt32)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
01351         if (capacity > 32)
01352             capacity = 32;
01353         capacity   *= 1024;
01354     } else {
01355         capacity    = 0;
01356     }
01357 
01358     return capacity;
01359 }
01360 
01361 
01362 /******************************************************************************
01363  * static methods for nsCacheEntryDescriptor
01364  *****************************************************************************/
01365 #ifdef XP_MAC
01366 #pragma mark -
01367 #endif
01368 
01369 void
01370 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
01371 {
01372     // ask entry to remove descriptor
01373     nsCacheEntry * entry       = descriptor->CacheEntry();
01374     PRBool         stillActive = entry->RemoveDescriptor(descriptor);
01375     nsresult       rv          = NS_OK;
01376 
01377     if (!entry->IsValid()) {
01378         rv = gService->ProcessPendingRequests(entry);
01379     }
01380 
01381     if (!stillActive) {
01382         gService->DeactivateEntry(entry);
01383     }
01384 }
01385 
01386 
01387 nsresult        
01388 nsCacheService::GetFileForEntry(nsCacheEntry *         entry,
01389                                 nsIFile **             result)
01390 {
01391     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
01392     if (!device)  return  NS_ERROR_UNEXPECTED;
01393     
01394     return device->GetFileForEntry(entry, result);
01395 }
01396 
01397 
01398 nsresult
01399 nsCacheService::OpenInputStreamForEntry(nsCacheEntry *     entry,
01400                                         nsCacheAccessMode  mode,
01401                                         PRUint32           offset,
01402                                         nsIInputStream  ** result)
01403 {
01404     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
01405     if (!device)  return  NS_ERROR_UNEXPECTED;
01406 
01407     return device->OpenInputStreamForEntry(entry, mode, offset, result);
01408 }
01409 
01410 nsresult
01411 nsCacheService::OpenOutputStreamForEntry(nsCacheEntry *     entry,
01412                                          nsCacheAccessMode  mode,
01413                                          PRUint32           offset,
01414                                          nsIOutputStream ** result)
01415 {
01416     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
01417     if (!device)  return  NS_ERROR_UNEXPECTED;
01418 
01419     return device->OpenOutputStreamForEntry(entry, mode, offset, result);
01420 }
01421 
01422 
01423 nsresult
01424 nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
01425 {
01426     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
01427     if (!device)  return  NS_ERROR_UNEXPECTED;
01428 
01429     return device->OnDataSizeChange(entry, deltaSize);
01430 }
01431 
01432 
01433 PRLock *
01434 nsCacheService::ServiceLock()
01435 {
01436     NS_ASSERTION(gService, "nsCacheService::gService is null.");
01437     return gService->mCacheServiceLock;
01438 }
01439 
01440 
01441 nsresult
01442 nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
01443 {
01444     entry->SetThread(PR_GetCurrentThread());
01445     entry->SetData(element);
01446     entry->TouchData();
01447     return NS_OK;
01448 }
01449 
01450 
01451 nsresult
01452 nsCacheService::ValidateEntry(nsCacheEntry * entry)
01453 {
01454     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
01455     if (!device)  return  NS_ERROR_UNEXPECTED;
01456 
01457     entry->MarkValid();
01458     nsresult rv = gService->ProcessPendingRequests(entry);
01459     NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
01460     // XXX what else should be done?
01461 
01462     return rv;
01463 }
01464 
01465 #ifdef XP_MAC
01466 #pragma mark -
01467 #endif
01468 
01469 
01470 void
01471 nsCacheService::DeactivateEntry(nsCacheEntry * entry)
01472 {
01473     nsresult  rv = NS_OK;
01474     NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
01475     nsCacheDevice * device = nsnull;
01476 
01477     if (mMaxDataSize < entry->DataSize() )     mMaxDataSize = entry->DataSize();
01478     if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
01479 
01480     if (entry->IsDoomed()) {
01481         // remove from Doomed list
01482         PR_REMOVE_AND_INIT_LINK(entry);
01483     } else if (entry->IsActive()) {
01484         // remove from active entries
01485         mActiveEntries.RemoveEntry(entry);
01486         entry->MarkInactive();
01487 
01488         // bind entry if necessary to store meta-data
01489         device = EnsureEntryHasDevice(entry); 
01490         if (!device) {
01491             NS_WARNING("DeactivateEntry: unable to bind active entry\n");
01492             return;
01493         }
01494     } else {
01495         // if mInitialized == PR_FALSE,
01496         // then we're shutting down and this state is okay.
01497         NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
01498     }
01499 
01500     device = entry->CacheDevice();
01501     if (device) {
01502         rv = device->DeactivateEntry(entry);
01503         if (NS_FAILED(rv)) {
01504             // increment deactivate failure count
01505             ++mDeactivateFailures;
01506         }
01507     } else {
01508         // increment deactivating unbound entry statistic
01509         ++mDeactivatedUnboundEntries;
01510         delete entry; // because no one else will
01511     }
01512 }
01513 
01514 
01515 nsresult
01516 nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
01517 {
01518     nsresult            rv = NS_OK;
01519     nsCacheRequest *    request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
01520     nsCacheRequest *    nextRequest;
01521     PRBool              newWriter = PR_FALSE;
01522     
01523     if (request == &entry->mRequestQ)  return NS_OK;    // no queued requests
01524 
01525     if (!entry->IsDoomed() && entry->IsInvalid()) {
01526         // 1st descriptor closed w/o MarkValid()
01527         NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
01528 
01529 #if DEBUG
01530         // verify no ACCESS_WRITE requests(shouldn't have any of these)
01531         while (request != &entry->mRequestQ) {
01532             NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
01533                          "ACCESS_WRITE request should have been given a new entry");
01534             request = (nsCacheRequest *)PR_NEXT_LINK(request);
01535         }
01536         request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);        
01537 #endif
01538         // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
01539         while (request != &entry->mRequestQ) {
01540             if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
01541                 newWriter = PR_TRUE;
01542                 break;
01543             }
01544 
01545             request = (nsCacheRequest *)PR_NEXT_LINK(request);
01546         }
01547         
01548         if (request == &entry->mRequestQ)   // no requests asked for ACCESS_READ_WRITE, back to top
01549             request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
01550         
01551         // XXX what should we do if there are only READ requests in queue?
01552         // XXX serialize their accesses, give them only read access, but force them to check validate flag?
01553         // XXX or do readers simply presume the entry is valid
01554     }
01555 
01556     nsCacheAccessMode  accessGranted = nsICache::ACCESS_NONE;
01557 
01558     while (request != &entry->mRequestQ) {
01559         nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
01560 
01561         if (request->mListener) {
01562 
01563             // Async request
01564             PR_REMOVE_AND_INIT_LINK(request);
01565 
01566             if (entry->IsDoomed()) {
01567                 rv = ProcessRequest(request, PR_FALSE, nsnull);
01568                 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
01569                     rv = NS_OK;
01570                 else
01571                     delete request;
01572 
01573                 if (NS_FAILED(rv)) {
01574                     // XXX what to do?
01575                 }
01576             } else if (entry->IsValid() || newWriter) {
01577                 rv = entry->RequestAccess(request, &accessGranted);
01578                 NS_ASSERTION(NS_SUCCEEDED(rv),
01579                              "if entry is valid, RequestAccess must succeed.");
01580                 // XXX if (newWriter)  NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
01581 
01582                 // entry->CreateDescriptor dequeues request, and queues descriptor
01583                 nsCOMPtr<nsICacheEntryDescriptor> descriptor;
01584                 rv = entry->CreateDescriptor(request,
01585                                              accessGranted,
01586                                              getter_AddRefs(descriptor));
01587                 
01588                 // post call to listener to report error or descriptor
01589                 rv = NotifyListener(request, descriptor, accessGranted, rv);
01590                 delete request;
01591                 if (NS_FAILED(rv)) {
01592                     // XXX what to do?
01593                 }
01594                 
01595             } else {
01596                 // XXX bad state
01597             }
01598         } else {
01599 
01600             // Synchronous request
01601             request->WakeUp();
01602         }
01603         if (newWriter)  break;  // process remaining requests after validation
01604         request = nextRequest;
01605     }
01606 
01607     return NS_OK;
01608 }
01609 
01610 
01611 void
01612 nsCacheService::ClearPendingRequests(nsCacheEntry * entry)
01613 {
01614     nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
01615     
01616     while (request != &entry->mRequestQ) {
01617         nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request);
01618 
01619         // XXX we're just dropping these on the floor for now...definitely wrong.
01620         PR_REMOVE_AND_INIT_LINK(request);
01621         delete request;
01622         request = next;
01623     }
01624 }
01625 
01626 
01627 void
01628 nsCacheService::ClearDoomList()
01629 {
01630     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
01631 
01632     while (entry != &mDoomedEntries) {
01633         nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
01634         
01635          entry->DetachDescriptors();
01636          DeactivateEntry(entry);
01637          entry = next;
01638     }        
01639 }
01640 
01641 
01642 void
01643 nsCacheService::ClearActiveEntries()
01644 {
01645     // XXX really we want a different finalize callback for mActiveEntries
01646     PL_DHashTableEnumerate(&mActiveEntries.table, DeactivateAndClearEntry, nsnull);
01647     mActiveEntries.Shutdown();
01648 }
01649 
01650 
01651 PLDHashOperator PR_CALLBACK
01652 nsCacheService::DeactivateAndClearEntry(PLDHashTable *    table,
01653                                         PLDHashEntryHdr * hdr,
01654                                         PRUint32          number,
01655                                         void *            arg)
01656 {
01657     nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
01658     NS_ASSERTION(entry, "### active entry = nsnull!");
01659     gService->ClearPendingRequests(entry);
01660     entry->DetachDescriptors();
01661     
01662     entry->MarkInactive();  // so we don't call Remove() while we're enumerating
01663     gService->DeactivateEntry(entry);
01664     
01665     return PL_DHASH_REMOVE; // and continue enumerating
01666 }
01667 
01668 
01669 void
01670 nsCacheService::DoomActiveEntries()
01671 {
01672     nsAutoVoidArray array;
01673 
01674     PL_DHashTableEnumerate(&mActiveEntries.table, RemoveActiveEntry, &array);
01675 
01676     PRUint32 count = array.Count();
01677     for (PRUint32 i=0; i < count; ++i)
01678         DoomEntry_Internal((nsCacheEntry *) array[i]);
01679 }
01680 
01681 
01682 PLDHashOperator PR_CALLBACK
01683 nsCacheService::RemoveActiveEntry(PLDHashTable *    table,
01684                                   PLDHashEntryHdr * hdr,
01685                                   PRUint32          number,
01686                                   void *            arg)
01687 {
01688     nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
01689     NS_ASSERTION(entry, "### active entry = nsnull!");
01690 
01691     nsVoidArray * array = (nsVoidArray *) arg;
01692     NS_ASSERTION(array, "### array = nsnull!");
01693     array->AppendElement(entry);
01694 
01695     // entry is being removed from the active entry list
01696     entry->MarkInactive();
01697     return PL_DHASH_REMOVE; // and continue enumerating
01698 }
01699 
01700 
01701 #if defined(PR_LOGGING)
01702 void
01703 nsCacheService::LogCacheStatistics()
01704 {
01705     PRUint32 hitPercentage = (PRUint32)((((double)mCacheHits) /
01706         ((double)(mCacheHits + mCacheMisses))) * 100);
01707     CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
01708     CACHE_LOG_ALWAYS(("    TotalEntries   = %d\n", mTotalEntries));
01709     CACHE_LOG_ALWAYS(("    Cache Hits     = %d\n", mCacheHits));
01710     CACHE_LOG_ALWAYS(("    Cache Misses   = %d\n", mCacheMisses));
01711     CACHE_LOG_ALWAYS(("    Cache Hit %%    = %d%%\n", hitPercentage));
01712     CACHE_LOG_ALWAYS(("    Max Key Length = %d\n", mMaxKeyLength));
01713     CACHE_LOG_ALWAYS(("    Max Meta Size  = %d\n", mMaxMetaSize));
01714     CACHE_LOG_ALWAYS(("    Max Data Size  = %d\n", mMaxDataSize));
01715     CACHE_LOG_ALWAYS(("\n"));
01716     CACHE_LOG_ALWAYS(("    Deactivate Failures         = %d\n",
01717                       mDeactivateFailures));
01718     CACHE_LOG_ALWAYS(("    Deactivated Unbound Entries = %d\n",
01719                       mDeactivatedUnboundEntries));
01720 }
01721 #endif