author | Mitchell Field <mitchell.field@live.com.au> |
Wed, 02 Jun 2010 01:56:00 -0400 | |
changeset 43113 | d8dc49d5bd609668b3c4fadd6c1df12d5da20547 |
parent 43112 | 28086cf6ede84358c8d4a05be6596594a0853e5f |
child 43114 | 01f8d7c7654d1aa8d22c5e463dd136ddc1a42b2a |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | biesi |
bugs | 542222 |
milestone | 1.9.3a5pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/build/Makefile.in +++ b/netwerk/build/Makefile.in @@ -30,47 +30,46 @@ # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -MODULE = necko -LIBRARY_NAME = necko +MODULE = necko +LIBRARY_NAME = necko EXPORT_LIBRARY = 1 -IS_COMPONENT = 1 -MODULE_NAME = necko -GRE_MODULE = 1 +IS_COMPONENT = 1 +MODULE_NAME = necko +GRE_MODULE = 1 LIBXUL_LIBRARY = 1 - -CPPSRCS = nsNetModule.cpp -EXPORTS = nsNetCID.h +CPPSRCS = nsNetModule.cpp +EXPORTS = nsNetCID.h SHARED_LIBRARY_LIBS = \ - ../base/src/$(LIB_PREFIX)neckobase_s.$(LIB_SUFFIX) \ - ../dns/src/$(LIB_PREFIX)neckodns_s.$(LIB_SUFFIX) \ - ../socket/base/$(LIB_PREFIX)neckosocket_s.$(LIB_SUFFIX) \ - ../streamconv/src/$(LIB_PREFIX)nkconv_s.$(LIB_SUFFIX) \ - ../streamconv/converters/$(LIB_PREFIX)nkcnvts_s.$(LIB_SUFFIX) \ - ../mime/src/$(LIB_PREFIX)nkmime_s.$(LIB_SUFFIX) \ - ../cache/src/$(LIB_PREFIX)nkcache_s.$(LIB_SUFFIX) \ - ../protocol/about/src/$(LIB_PREFIX)nkabout_s.$(LIB_SUFFIX) \ - $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \ - ../protocol/$(d)/src/$(LIB_PREFIX)nk$(d)_s.$(LIB_SUFFIX)) \ - $(NULL) + ../base/src/$(LIB_PREFIX)neckobase_s.$(LIB_SUFFIX) \ + ../dns/$(LIB_PREFIX)neckodns_s.$(LIB_SUFFIX) \ + ../socket/$(LIB_PREFIX)neckosocket_s.$(LIB_SUFFIX) \ + ../streamconv/src/$(LIB_PREFIX)nkconv_s.$(LIB_SUFFIX) \ + ../streamconv/converters/$(LIB_PREFIX)nkcnvts_s.$(LIB_SUFFIX) \ + ../mime/$(LIB_PREFIX)nkmime_s.$(LIB_SUFFIX) \ + ../cache/$(LIB_PREFIX)nkcache_s.$(LIB_SUFFIX) \ + ../protocol/about/$(LIB_PREFIX)nkabout_s.$(LIB_SUFFIX) \ + $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \ + ../protocol/$(d)/$(LIB_PREFIX)nk$(d)_s.$(LIB_SUFFIX)) \ + $(NULL) ifeq ($(OS_ARCH),WINNT) SHARED_LIBRARY_LIBS += \ ../system/win32/$(LIB_PREFIX)neckosystem_s.$(LIB_SUFFIX) endif ifeq ($(OS_ARCH),WINCE) SHARED_LIBRARY_LIBS += \ @@ -82,28 +81,28 @@ ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) ../system/mac/$(LIB_PREFIX)neckosystem_s.$(LIB_SUFFIX) endif ifdef MOZ_ENABLE_LIBCONIC SHARED_LIBRARY_LIBS += \ ../system/maemo/$(LIB_PREFIX)neckosystem_s.$(LIB_SUFFIX) endif -LOCAL_INCLUDES = \ - -I$(srcdir)/../base/src \ - -I$(srcdir)/../dns/src \ - -I$(srcdir)/../socket/base \ - -I$(srcdir)/../streamconv/src \ - -I$(srcdir)/../streamconv/converters \ - -I$(srcdir)/../mime/src \ - -I$(srcdir)/../cache/src \ - -I$(srcdir)/../protocol/about/src \ - $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \ - -I$(srcdir)/../protocol/$(d)/src) \ - $(NULL) +LOCAL_INCLUDES = \ + -I$(srcdir)/../base/src \ + -I$(srcdir)/../dns \ + -I$(srcdir)/../socket \ + -I$(srcdir)/../streamconv/src \ + -I$(srcdir)/../streamconv/converters \ + -I$(srcdir)/../mime \ + -I$(srcdir)/../cache \ + -I$(srcdir)/../protocol/about \ + $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \ + -I$(srcdir)/../protocol/$(d)) \ + $(NULL) ifeq ($(OS_ARCH),WINNT) LOCAL_INCLUDES += -I$(srcdir)/../system/win32 endif ifeq ($(OS_ARCH),WINCE) LOCAL_INCLUDES += -I$(srcdir)/../system/wince endif @@ -113,67 +112,67 @@ ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) endif ifdef MOZ_ENABLE_LIBCONIC LOCAL_INCLUDES += -I$(srcdir)/../system/maemo endif ifdef NECKO_COOKIES SHARED_LIBRARY_LIBS += \ - ../cookie/src/$(LIB_PREFIX)neckocookie_s.$(LIB_SUFFIX) \ - $(NULL) -LOCAL_INCLUDES += -I$(srcdir)/../cookie/src + ../cookie/$(LIB_PREFIX)neckocookie_s.$(LIB_SUFFIX) \ + $(NULL) +LOCAL_INCLUDES += -I$(srcdir)/../cookie endif ifdef NECKO_WIFI SHARED_LIBRARY_LIBS += \ - ../wifi/src/$(LIB_PREFIX)neckowifi_s.$(LIB_SUFFIX) \ - $(NULL) -LOCAL_INCLUDES += -I$(srcdir)/../wifi/src + ../wifi/$(LIB_PREFIX)neckowifi_s.$(LIB_SUFFIX) \ + $(NULL) +LOCAL_INCLUDES += -I$(srcdir)/../wifi ifeq ($(OS_ARCH),SunOS) OS_LIBS += $(GLIB_LIBS) endif endif ifdef MOZ_STORAGE DEFINES += -DNECKO_OFFLINE_CACHE endif EXTRA_DSO_LDOPTS = \ - $(LIBS_DIR) \ - $(EXTRA_DSO_LIBS) \ - $(MOZ_UNICHARUTIL_LIBS) \ - $(MOZ_COMPONENT_LIBS) \ - $(ZLIB_LIBS) \ - $(NULL) + $(LIBS_DIR) \ + $(EXTRA_DSO_LIBS) \ + $(MOZ_UNICHARUTIL_LIBS) \ + $(MOZ_COMPONENT_LIBS) \ + $(ZLIB_LIBS) \ + $(NULL) ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) EXTRA_DSO_LDOPTS += \ - -framework SystemConfiguration \ - $(TK_LIBS) \ - $(NULL) + -framework SystemConfiguration \ + $(TK_LIBS) \ + $(NULL) endif ifdef MOZ_ENABLE_LIBCONIC EXTRA_DSO_LDOPTS += \ - $(LIBCONIC_LIBS) \ - $(MOZ_DBUS_LIBS) \ - $(NULL) + $(LIBCONIC_LIBS) \ + $(MOZ_DBUS_LIBS) \ + $(NULL) endif ifeq ($(OS_ARCH),AIX) EXTRA_DSO_LDOPTS += -lodm -lcfg endif include $(topsrcdir)/config/rules.mk ifeq ($(OS_ARCH),WINNT) -OS_LIBS += $(call EXPAND_LIBNAME,ole32 shell32) +OS_LIBS += $(call EXPAND_LIBNAME,ole32 shell32) endif ifeq ($(OS_ARCH),WINCE) -OS_LIBS += $(call EXPAND_LIBNAME,cellcore ws2) +OS_LIBS += $(call EXPAND_LIBNAME,cellcore ws2) endif DEFINES += -DIMPL_NS_NET
--- a/netwerk/cache/Makefile.in +++ b/netwerk/cache/Makefile.in @@ -30,21 +30,73 @@ # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** -DEPTH = ../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -MODULE = nkcache -DIRS = public src +MODULE = nkcache +LIBRARY_NAME = nkcache_s +LIBXUL_LIBRARY = 1 +XPIDL_MODULE = necko_cache +GRE_MODULE = 1 + +FORCE_STATIC_LIB = 1 + +XPIDLSRCS = \ + nsICache.idl \ + nsICacheEntryDescriptor.idl \ + nsICacheListener.idl \ + nsICacheService.idl \ + nsICacheSession.idl \ + nsICacheVisitor.idl \ + $(NULL) + +EXPORTS = \ + nsCacheService.h \ + $(NULL) + +CPPSRCS = \ + nsCache.cpp \ + nsCacheEntry.cpp \ + nsCacheEntryDescriptor.cpp \ + nsCacheMetaData.cpp \ + nsCacheService.cpp \ + nsCacheSession.cpp \ + nsMemoryCacheDevice.cpp \ + $(NULL) + +ifdef NECKO_DISK_CACHE +CPPSRCS += \ + nsDiskCacheBinding.cpp \ + nsDiskCacheBlockFile.cpp \ + nsDiskCacheDevice.cpp \ + nsDiskCacheEntry.cpp \ + nsDiskCacheMap.cpp \ + nsDiskCacheStreams.cpp \ + nsDeleteDir.cpp \ + $(NULL) +endif + +ifdef MOZ_STORAGE +CPPSRCS += \ + nsDiskCacheDeviceSQL.cpp \ + $(NULL) + +DEFINES += -DNECKO_OFFLINE_CACHE +endif + +LOCAL_INCLUDES = \ + -I$(srcdir)/../base/src \ + $(NULL) include $(topsrcdir)/config/rules.mk DEFINES += -DIMPL_NS_NET
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCache.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCache.cpp, released + * March 18, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick C. Beard <beard@netscape.com> + * Darin Fisher <darin@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCache.h" +#include "nsReadableUtils.h" +#include "nsDependentSubstring.h" +#include "nsString.h" + + +/** + * Cache Service Utility Functions + */ + +#if defined(PR_LOGGING) +PRLogModuleInfo * gCacheLog = nsnull; + + +void +CacheLogInit() +{ + if (gCacheLog) return; + gCacheLog = PR_NewLogModule("cache"); + NS_ASSERTION(gCacheLog, "\nfailed to allocate cache log.\n"); +} + + +void +CacheLogPrintPath(PRLogModuleLevel level, const char * format, nsIFile * item) +{ + nsCAutoString path; + nsresult rv = item->GetNativePath(path); + if (NS_SUCCEEDED(rv)) { + PR_LOG(gCacheLog, level, (format, path.get())); + } else { + PR_LOG(gCacheLog, level, ("GetNativePath failed: %x", rv)); + } +} + +#endif + + +PRUint32 +SecondsFromPRTime(PRTime prTime) +{ + PRInt64 microSecondsPerSecond, intermediateResult; + PRUint32 seconds; + + LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC); + LL_DIV(intermediateResult, prTime, microSecondsPerSecond); + LL_L2UI(seconds, intermediateResult); + return seconds; +} + + +PRTime +PRTimeFromSeconds(PRUint32 seconds) +{ + PRInt64 microSecondsPerSecond, intermediateResult; + PRTime prTime; + + LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC); + LL_UI2L(intermediateResult, seconds); + LL_MUL(prTime, intermediateResult, microSecondsPerSecond); + return prTime; +} + + +nsresult +ClientIDFromCacheKey(const nsACString& key, char ** result) +{ + nsresult rv = NS_OK; + *result = nsnull; + + nsReadingIterator<char> colon; + key.BeginReading(colon); + + nsReadingIterator<char> start; + key.BeginReading(start); + + nsReadingIterator<char> end; + key.EndReading(end); + + if (FindCharInReadable(':', colon, end)) { + *result = ToNewCString( Substring(start, colon)); + if (!*result) rv = NS_ERROR_OUT_OF_MEMORY; + } else { + NS_ASSERTION(PR_FALSE, "FindCharInRead failed to find ':'"); + rv = NS_ERROR_UNEXPECTED; + } + return rv; +} + + +nsresult +ClientKeyFromCacheKey(const nsCString& key, nsACString &result) +{ + nsresult rv = NS_OK; + + nsReadingIterator<char> start; + key.BeginReading(start); + + nsReadingIterator<char> end; + key.EndReading(end); + + if (FindCharInReadable(':', start, end)) { + ++start; // advance past clientID ':' delimiter + result.Assign(Substring(start, end)); + } else { + NS_ASSERTION(PR_FALSE, "FindCharInRead failed to find ':'"); + rv = NS_ERROR_UNEXPECTED; + result.Truncate(0); + } + return rv; +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCache.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCache.h, released + * March 18, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick C. Beard <beard@netscape.com> + * Darin Fisher <darin@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Cache Service Utility Functions + */ + +#ifndef _nsCache_h_ +#define _nsCache_h_ + +#include "nsISupports.h" +#include "nsIFile.h" +#include "nsAString.h" +#include "prtime.h" +#include "nsError.h" +#include "prlog.h" + +// PR_LOG args = "format string", arg, arg, ... +#if defined(PR_LOGGING) +extern PRLogModuleInfo * gCacheLog; +void CacheLogInit(); +void CacheLogPrintPath(PRLogModuleLevel level, + const char * format, + nsIFile * item); +#define CACHE_LOG_INIT() CacheLogInit() +#define CACHE_LOG_ALWAYS(args) PR_LOG(gCacheLog, PR_LOG_ALWAYS, args) +#define CACHE_LOG_ERROR(args) PR_LOG(gCacheLog, PR_LOG_ERROR, args) +#define CACHE_LOG_WARNING(args) PR_LOG(gCacheLog, PR_LOG_WARNING, args) +#define CACHE_LOG_DEBUG(args) PR_LOG(gCacheLog, PR_LOG_DEBUG, args) +#define CACHE_LOG_PATH(level, format, item) \ + CacheLogPrintPath(level, format, item) +#else +#define CACHE_LOG_INIT() {} +#define CACHE_LOG_ALWAYS(args) {} +#define CACHE_LOG_ERROR(args) {} +#define CACHE_LOG_WARNING(args) {} +#define CACHE_LOG_DEBUG(args) {} +#define CACHE_LOG_PATH(level, format, item) {} +#endif + + +extern PRUint32 SecondsFromPRTime(PRTime prTime); +extern PRTime PRTimeFromSeconds(PRUint32 seconds); + + +extern nsresult ClientIDFromCacheKey(const nsACString& key, char ** result); +extern nsresult ClientKeyFromCacheKey(const nsCString& key, nsACString &result); + + +#endif // _nsCache_h
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheDevice.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheDevice.h, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan, 22-February-2001 + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsCacheDevice_h_ +#define _nsCacheDevice_h_ + +#include "nspr.h" +#include "nsError.h" +#include "nsICache.h" + +class nsIFile; +class nsCString; +class nsCacheEntry; +class nsICacheVisitor; +class nsIInputStream; +class nsIOutputStream; + +/****************************************************************************** +* nsCacheDevice +*******************************************************************************/ +class nsCacheDevice { +public: + nsCacheDevice() { MOZ_COUNT_CTOR(nsCacheDevice); } + virtual ~nsCacheDevice() { MOZ_COUNT_DTOR(nsCacheDevice); } + + virtual nsresult Init() = 0; + virtual nsresult Shutdown() = 0; + + virtual const char * GetDeviceID(void) = 0; + virtual nsCacheEntry * FindEntry( nsCString * key, PRBool *collision ) = 0; + + virtual nsresult DeactivateEntry( nsCacheEntry * entry ) = 0; + virtual nsresult BindEntry( nsCacheEntry * entry ) = 0; + virtual void DoomEntry( nsCacheEntry * entry ) = 0; + + virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIInputStream ** result) = 0; + + virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIOutputStream ** result) = 0; + + virtual nsresult GetFileForEntry( nsCacheEntry * entry, + nsIFile ** result ) = 0; + + virtual nsresult OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize ) = 0; + + virtual nsresult Visit(nsICacheVisitor * visitor) = 0; + + /** + * Device must evict entries associated with clientID. If clientID == nsnull, all + * entries must be evicted. Active entries must be doomed, rather than evicted. + */ + virtual nsresult EvictEntries(const char * clientID) = 0; +}; + +#endif // _nsCacheDevice_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheEntry.cpp @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheEntry.cpp, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#include "nspr.h" +#include "nsCacheEntry.h" +#include "nsCacheEntryDescriptor.h" +#include "nsCacheMetaData.h" +#include "nsCacheRequest.h" +#include "nsThreadUtils.h" +#include "nsError.h" +#include "nsICacheService.h" +#include "nsCache.h" +#include "nsCacheService.h" +#include "nsCacheDevice.h" +#include "nsCRT.h" + + +nsCacheEntry::nsCacheEntry(nsCString * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy) + : mKey(key), + mFetchCount(0), + mLastFetched(0), + mLastModified(0), + mExpirationTime(NO_EXPIRATION_TIME), + mFlags(0), + mDataSize(0), + mCacheDevice(nsnull), + mData(nsnull) +{ + MOZ_COUNT_CTOR(nsCacheEntry); + PR_INIT_CLIST(this); + PR_INIT_CLIST(&mRequestQ); + PR_INIT_CLIST(&mDescriptorQ); + + if (streamBased) MarkStreamBased(); + SetStoragePolicy(storagePolicy); +} + + +nsCacheEntry::~nsCacheEntry() +{ + MOZ_COUNT_DTOR(nsCacheEntry); + delete mKey; + + if (mData) + nsCacheService::ReleaseObject_Locked(mData, mThread); +} + + +nsresult +nsCacheEntry::Create( const char * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy, + nsCacheDevice * device, + nsCacheEntry ** result) +{ + nsCString* newKey = new nsCString(key); + if (!newKey) return NS_ERROR_OUT_OF_MEMORY; + + nsCacheEntry* entry = new nsCacheEntry(newKey, streamBased, storagePolicy); + if (!entry) { delete newKey; return NS_ERROR_OUT_OF_MEMORY; } + + entry->SetCacheDevice(device); + + *result = entry; + return NS_OK; +} + + +void +nsCacheEntry::Fetched() +{ + mLastFetched = SecondsFromPRTime(PR_Now()); + ++mFetchCount; + MarkEntryDirty(); +} + + +const char * +nsCacheEntry::GetDeviceID() +{ + if (mCacheDevice) return mCacheDevice->GetDeviceID(); + return nsnull; +} + + +void +nsCacheEntry::TouchData() +{ + mLastModified = SecondsFromPRTime(PR_Now()); + MarkDataDirty(); +} + + +void +nsCacheEntry::SetData(nsISupports * data) +{ + if (mData) { + nsCacheService::ReleaseObject_Locked(mData, mThread); + mData = nsnull; + } + + if (data) { + NS_ADDREF(mData = data); + mThread = do_GetCurrentThread(); + } +} + + +void +nsCacheEntry::TouchMetaData() +{ + mLastModified = SecondsFromPRTime(PR_Now()); + MarkMetaDataDirty(); +} + + +/** + * cache entry states + * 0 descriptors (new entry) + * 0 descriptors (existing, bound entry) + * n descriptors (existing, bound entry) valid + * n descriptors (existing, bound entry) not valid (wait until valid or doomed) + */ + +nsresult +nsCacheEntry::RequestAccess(nsCacheRequest * request, nsCacheAccessMode *accessGranted) +{ + nsresult rv = NS_OK; + + if (!IsInitialized()) { + // brand new, unbound entry + request->mKey = nsnull; // steal ownership of the key string + if (request->IsStreamBased()) MarkStreamBased(); + MarkInitialized(); + + *accessGranted = request->AccessRequested() & nsICache::ACCESS_WRITE; + NS_ASSERTION(*accessGranted, "new cache entry for READ-ONLY request"); + PR_APPEND_LINK(request, &mRequestQ); + return rv; + } + + if (IsDoomed()) return NS_ERROR_CACHE_ENTRY_DOOMED; + + if (IsStreamData() != request->IsStreamBased()) { + *accessGranted = nsICache::ACCESS_NONE; + return request->IsStreamBased() ? + NS_ERROR_CACHE_DATA_IS_NOT_STREAM : NS_ERROR_CACHE_DATA_IS_STREAM; + } + + if (PR_CLIST_IS_EMPTY(&mDescriptorQ)) { + // 1st descriptor for existing bound entry + *accessGranted = request->AccessRequested(); + if (*accessGranted & nsICache::ACCESS_WRITE) { + MarkInvalid(); + } else { + MarkValid(); + } + } else { + // nth request for existing, bound entry + *accessGranted = request->AccessRequested() & ~nsICache::ACCESS_WRITE; + if (!IsValid()) + rv = NS_ERROR_CACHE_WAIT_FOR_VALIDATION; + } + PR_APPEND_LINK(request,&mRequestQ); + + return rv; +} + + +nsresult +nsCacheEntry::CreateDescriptor(nsCacheRequest * request, + nsCacheAccessMode accessGranted, + nsICacheEntryDescriptor ** result) +{ + NS_ENSURE_ARG_POINTER(request && result); + + nsCacheEntryDescriptor * descriptor = + new nsCacheEntryDescriptor(this, accessGranted); + + // XXX check request is on q + PR_REMOVE_AND_INIT_LINK(request); // remove request regardless of success + + if (descriptor == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + + PR_APPEND_LINK(descriptor, &mDescriptorQ); + + NS_ADDREF(*result = descriptor); + return NS_OK; +} + + +PRBool +nsCacheEntry::RemoveRequest(nsCacheRequest * request) +{ + // XXX if debug: verify this request belongs to this entry + PR_REMOVE_AND_INIT_LINK(request); + + // return true if this entry should stay active + return !((PR_CLIST_IS_EMPTY(&mRequestQ)) && + (PR_CLIST_IS_EMPTY(&mDescriptorQ))); +} + + +PRBool +nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor * descriptor) +{ + NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!"); + PR_REMOVE_AND_INIT_LINK(descriptor); + descriptor->ClearCacheEntry(); + + if (!PR_CLIST_IS_EMPTY(&mDescriptorQ)) + return PR_TRUE; // stay active if we still have open descriptors + + if (PR_CLIST_IS_EMPTY(&mRequestQ)) + return PR_FALSE; // no descriptors or requests, we can deactivate + + return PR_TRUE; // find next best request to give a descriptor to +} + + +void +nsCacheEntry::DetachDescriptors(void) +{ + nsCacheEntryDescriptor * descriptor = + (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ); + + while (descriptor != &mDescriptorQ) { + nsCacheEntryDescriptor * nextDescriptor = + (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor); + + descriptor->ClearCacheEntry(); + PR_REMOVE_AND_INIT_LINK(descriptor); + descriptor = nextDescriptor; + } +} + + +/****************************************************************************** + * nsCacheEntryInfo - for implementing about:cache + *****************************************************************************/ + +NS_IMPL_ISUPPORTS1(nsCacheEntryInfo, nsICacheEntryInfo) + + +NS_IMETHODIMP +nsCacheEntryInfo::GetClientID(char ** clientID) +{ + NS_ENSURE_ARG_POINTER(clientID); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return ClientIDFromCacheKey(*mCacheEntry->Key(), clientID); +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetDeviceID(char ** deviceID) +{ + NS_ENSURE_ARG_POINTER(deviceID); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *deviceID = NS_strdup(mCacheEntry->GetDeviceID()); + return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetKey(nsACString &key) +{ + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return ClientKeyFromCacheKey(*mCacheEntry->Key(), key); +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetFetchCount(PRInt32 * fetchCount) +{ + NS_ENSURE_ARG_POINTER(fetchCount); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *fetchCount = mCacheEntry->FetchCount(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetLastFetched(PRUint32 * lastFetched) +{ + NS_ENSURE_ARG_POINTER(lastFetched); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *lastFetched = mCacheEntry->LastFetched(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetLastModified(PRUint32 * lastModified) +{ + NS_ENSURE_ARG_POINTER(lastModified); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *lastModified = mCacheEntry->LastModified(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetExpirationTime(PRUint32 * expirationTime) +{ + NS_ENSURE_ARG_POINTER(expirationTime); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *expirationTime = mCacheEntry->ExpirationTime(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::GetDataSize(PRUint32 * dataSize) +{ + NS_ENSURE_ARG_POINTER(dataSize); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *dataSize = mCacheEntry->DataSize(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryInfo::IsStreamBased(PRBool * result) +{ + NS_ENSURE_ARG_POINTER(result); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->IsStreamData(); + return NS_OK; +} + + +/****************************************************************************** + * nsCacheEntryHashTable + *****************************************************************************/ + +PLDHashTableOps +nsCacheEntryHashTable::ops = +{ + PL_DHashAllocTable, + PL_DHashFreeTable, + HashKey, + MatchEntry, + MoveEntry, + ClearEntry, + PL_DHashFinalizeStub +}; + + +nsCacheEntryHashTable::nsCacheEntryHashTable() + : initialized(PR_FALSE) +{ + MOZ_COUNT_CTOR(nsCacheEntryHashTable); +} + + +nsCacheEntryHashTable::~nsCacheEntryHashTable() +{ + MOZ_COUNT_DTOR(nsCacheEntryHashTable); + if (initialized) + Shutdown(); +} + + +nsresult +nsCacheEntryHashTable::Init() +{ + nsresult rv = NS_OK; + initialized = PL_DHashTableInit(&table, &ops, nsnull, + sizeof(nsCacheEntryHashTableEntry), 512); + + if (!initialized) rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} + +void +nsCacheEntryHashTable::Shutdown() +{ + if (initialized) { + PL_DHashTableFinish(&table); + initialized = PR_FALSE; + } +} + + +nsCacheEntry * +nsCacheEntryHashTable::GetEntry( const nsCString * key) +{ + PLDHashEntryHdr *hashEntry; + nsCacheEntry *result = nsnull; + + NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); + if (!initialized) return nsnull; + + hashEntry = PL_DHashTableOperate(&table, key, PL_DHASH_LOOKUP); + if (PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { + result = ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry; + } + return result; +} + + +nsresult +nsCacheEntryHashTable::AddEntry( nsCacheEntry *cacheEntry) +{ + PLDHashEntryHdr *hashEntry; + + NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); + if (!initialized) return NS_ERROR_NOT_INITIALIZED; + if (!cacheEntry) return NS_ERROR_NULL_POINTER; + + hashEntry = PL_DHashTableOperate(&table, cacheEntry->mKey, PL_DHASH_ADD); +#ifndef DEBUG_dougt + NS_ASSERTION(((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry == 0, + "### nsCacheEntryHashTable::AddEntry - entry already used"); +#endif + ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = cacheEntry; + + return NS_OK; +} + + +void +nsCacheEntryHashTable::RemoveEntry( nsCacheEntry *cacheEntry) +{ + NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); + NS_ASSERTION(cacheEntry, "### cacheEntry == nsnull"); + + if (!initialized) return; // NS_ERROR_NOT_INITIALIZED + +#if DEBUG + // XXX debug code to make sure we have the entry we're trying to remove + nsCacheEntry *check = GetEntry(cacheEntry->mKey); + NS_ASSERTION(check == cacheEntry, "### Attempting to remove unknown cache entry!!!"); +#endif + (void) PL_DHashTableOperate(&table, cacheEntry->mKey, PL_DHASH_REMOVE); +} + + +void +nsCacheEntryHashTable::VisitEntries( PLDHashEnumerator etor, void *arg) +{ + NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); + if (!initialized) return; // NS_ERROR_NOT_INITIALIZED + PL_DHashTableEnumerate(&table, etor, arg); +} + + +/** + * hash table operation callback functions + */ + +PLDHashNumber +nsCacheEntryHashTable::HashKey( PLDHashTable *table, const void *key) +{ + return PL_DHashStringKey(table,((nsCString *)key)->get()); +} + +PRBool +nsCacheEntryHashTable::MatchEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr * hashEntry, + const void * key) +{ + NS_ASSERTION(key != nsnull, "### nsCacheEntryHashTable::MatchEntry : null key"); + nsCacheEntry *cacheEntry = ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry; + + return cacheEntry->mKey->Equals(*(nsCString *)key); +} + + +void +nsCacheEntryHashTable::MoveEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + ((nsCacheEntryHashTableEntry *)to)->cacheEntry = + ((nsCacheEntryHashTableEntry *)from)->cacheEntry; +} + + +void +nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */, + PLDHashEntryHdr * hashEntry) +{ + ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = 0; +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheEntry.h @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheEntry.h, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsCacheEntry_h_ +#define _nsCacheEntry_h_ + +#include "nsICache.h" +#include "nsICacheEntryDescriptor.h" +#include "nsIThread.h" +#include "nsCacheMetaData.h" + +#include "nspr.h" +#include "pldhash.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAString.h" + +class nsCacheDevice; +class nsCacheMetaData; +class nsCacheRequest; +class nsCacheEntryDescriptor; + +#define NO_EXPIRATION_TIME 0xFFFFFFFF + +/****************************************************************************** +* nsCacheEntry +*******************************************************************************/ +class nsCacheEntry : public PRCList +{ +public: + + nsCacheEntry(nsCString * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy); + ~nsCacheEntry(); + + + static nsresult Create( const char * key, + PRBool streamBased, + nsCacheStoragePolicy storagePolicy, + nsCacheDevice * device, + nsCacheEntry ** result); + + nsCString * Key() { return mKey; } + + PRInt32 FetchCount() { return mFetchCount; } + void SetFetchCount( PRInt32 count) { mFetchCount = count; } + void Fetched(); + + PRUint32 LastFetched() { return mLastFetched; } + void SetLastFetched( PRUint32 lastFetched) { mLastFetched = lastFetched; } + + PRUint32 LastModified() { return mLastModified; } + void SetLastModified( PRUint32 lastModified) { mLastModified = lastModified; } + + PRUint32 ExpirationTime() { return mExpirationTime; } + void SetExpirationTime( PRUint32 expires) { mExpirationTime = expires; } + + PRUint32 Size() { return mDataSize + mMetaData.Size(); } + + nsCacheDevice * CacheDevice() { return mCacheDevice; } + void SetCacheDevice( nsCacheDevice * device) { mCacheDevice = device; } + const char * GetDeviceID(); + + /** + * Data accessors + */ + nsISupports *Data() { return mData; } + void SetData( nsISupports * data); + + PRUint32 DataSize() { return mDataSize; } + void SetDataSize( PRUint32 size) { mDataSize = size; } + + void TouchData(); + + /** + * Meta data accessors + */ + const char * GetMetaDataElement( const char * key) { return mMetaData.GetElement(key); } + nsresult SetMetaDataElement( const char * key, + const char * value) { return mMetaData.SetElement(key, value); } + nsresult VisitMetaDataElements( nsICacheMetaDataVisitor * visitor) { return mMetaData.VisitElements(visitor); } + nsresult FlattenMetaData(char * buffer, PRUint32 bufSize) { return mMetaData.FlattenMetaData(buffer, bufSize); } + nsresult UnflattenMetaData(const char * buffer, PRUint32 bufSize) { return mMetaData.UnflattenMetaData(buffer, bufSize); } + PRUint32 MetaDataSize() { return mMetaData.Size(); } + + void TouchMetaData(); + + + /** + * Security Info accessors + */ + nsISupports* SecurityInfo() { return mSecurityInfo; } + void SetSecurityInfo( nsISupports * info) { mSecurityInfo = info; } + + + // XXX enumerate MetaData method + + + enum CacheEntryFlags { + eStoragePolicyMask = 0x000000FF, + eDoomedMask = 0x00000100, + eEntryDirtyMask = 0x00000200, + eDataDirtyMask = 0x00000400, + eMetaDataDirtyMask = 0x00000800, + eStreamDataMask = 0x00001000, + eActiveMask = 0x00002000, + eInitializedMask = 0x00004000, + eValidMask = 0x00008000, + eBindingMask = 0x00010000 + }; + + void MarkBinding() { mFlags |= eBindingMask; } + void ClearBinding() { mFlags &= ~eBindingMask; } + PRBool IsBinding() { return (mFlags & eBindingMask) != 0; } + + void MarkEntryDirty() { mFlags |= eEntryDirtyMask; } + void MarkEntryClean() { mFlags &= ~eEntryDirtyMask; } + void MarkDataDirty() { mFlags |= eDataDirtyMask; } + void MarkDataClean() { mFlags &= ~eDataDirtyMask; } + void MarkMetaDataDirty() { mFlags |= eMetaDataDirtyMask; } + void MarkMetaDataClean() { mFlags &= ~eMetaDataDirtyMask; } + void MarkStreamData() { mFlags |= eStreamDataMask; } + void MarkValid() { mFlags |= eValidMask; } + void MarkInvalid() { mFlags &= ~eValidMask; } + // void MarkAllowedInMemory() { mFlags |= eAllowedInMemoryMask; } + // void MarkAllowedOnDisk() { mFlags |= eAllowedOnDiskMask; } + + PRBool IsDoomed() { return (mFlags & eDoomedMask) != 0; } + PRBool IsEntryDirty() { return (mFlags & eEntryDirtyMask) != 0; } + PRBool IsDataDirty() { return (mFlags & eDataDirtyMask) != 0; } + PRBool IsMetaDataDirty() { return (mFlags & eMetaDataDirtyMask) != 0; } + PRBool IsStreamData() { return (mFlags & eStreamDataMask) != 0; } + PRBool IsActive() { return (mFlags & eActiveMask) != 0; } + PRBool IsInitialized() { return (mFlags & eInitializedMask) != 0; } + PRBool IsValid() { return (mFlags & eValidMask) != 0; } + PRBool IsInvalid() { return (mFlags & eValidMask) == 0; } + PRBool IsInUse() { return IsBinding() || + !(PR_CLIST_IS_EMPTY(&mRequestQ) && + PR_CLIST_IS_EMPTY(&mDescriptorQ)); } + PRBool IsNotInUse() { return !IsInUse(); } + + + PRBool IsAllowedInMemory() + { + return (StoragePolicy() == nsICache::STORE_ANYWHERE) || + (StoragePolicy() == nsICache::STORE_IN_MEMORY); + } + + PRBool IsAllowedOnDisk() + { + return (StoragePolicy() == nsICache::STORE_ANYWHERE) || + (StoragePolicy() == nsICache::STORE_ON_DISK) || + (StoragePolicy() == nsICache::STORE_ON_DISK_AS_FILE); + } + + PRBool IsAllowedOffline() + { + return (StoragePolicy() == nsICache::STORE_OFFLINE); + } + + nsCacheStoragePolicy StoragePolicy() + { + return (nsCacheStoragePolicy)(mFlags & eStoragePolicyMask); + } + + void SetStoragePolicy(nsCacheStoragePolicy policy) + { + NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy"); + mFlags &= ~eStoragePolicyMask; // clear storage policy bits + mFlags |= policy; + } + + + // methods for nsCacheService + nsresult RequestAccess( nsCacheRequest * request, nsCacheAccessMode *accessGranted); + nsresult CreateDescriptor( nsCacheRequest * request, + nsCacheAccessMode accessGranted, + nsICacheEntryDescriptor ** result); + + // nsresult Open(nsCacheRequest *request, nsICacheEntryDescriptor ** result); + // nsresult AsyncOpen(nsCacheRequest *request); + PRBool RemoveRequest( nsCacheRequest * request); + PRBool RemoveDescriptor( nsCacheEntryDescriptor * descriptor); + +private: + friend class nsCacheEntryHashTable; + friend class nsCacheService; + + void DetachDescriptors(void); + + // internal methods + void MarkDoomed() { mFlags |= eDoomedMask; } + void MarkStreamBased() { mFlags |= eStreamDataMask; } + void MarkInitialized() { mFlags |= eInitializedMask; } + void MarkActive() { mFlags |= eActiveMask; } + void MarkInactive() { mFlags &= ~eActiveMask; } + + nsCString * mKey; // 4 // XXX ask scc about const'ness + PRUint32 mFetchCount; // 4 + PRUint32 mLastFetched; // 4 + PRUint32 mLastModified; // 4 + PRUint32 mLastValidated; // 4 + PRUint32 mExpirationTime; // 4 + PRUint32 mFlags; // 4 + PRUint32 mDataSize; // 4 + nsCacheDevice * mCacheDevice; // 4 + nsCOMPtr<nsISupports> mSecurityInfo; // + nsISupports * mData; // strong ref + nsCOMPtr<nsIThread> mThread; + nsCacheMetaData mMetaData; // 4 + PRCList mRequestQ; // 8 + PRCList mDescriptorQ; // 8 +}; + + +/****************************************************************************** +* nsCacheEntryInfo +*******************************************************************************/ +class nsCacheEntryInfo : public nsICacheEntryInfo { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYINFO + + nsCacheEntryInfo(nsCacheEntry* entry) + : mCacheEntry(entry) + { + } + + virtual ~nsCacheEntryInfo() {} + void DetachEntry() { mCacheEntry = nsnull; } + +private: + nsCacheEntry * mCacheEntry; +}; + + +/****************************************************************************** +* nsCacheEntryHashTable +*******************************************************************************/ +typedef struct { + PLDHashNumber keyHash; + nsCacheEntry *cacheEntry; +} nsCacheEntryHashTableEntry; + + +class nsCacheEntryHashTable +{ +public: + nsCacheEntryHashTable(); + ~nsCacheEntryHashTable(); + + nsresult Init(); + void Shutdown(); + + nsCacheEntry *GetEntry( const nsCString * key); + nsresult AddEntry( nsCacheEntry *entry); + void RemoveEntry( nsCacheEntry *entry); + + void VisitEntries( PLDHashEnumerator etor, void *arg); + +private: + // PLDHashTable operation callbacks + static PLDHashNumber HashKey( PLDHashTable *table, const void *key); + + static PRBool MatchEntry( PLDHashTable * table, + const PLDHashEntryHdr * entry, + const void * key); + + static void MoveEntry( PLDHashTable *table, + const PLDHashEntryHdr *from, + PLDHashEntryHdr *to); + + static void ClearEntry( PLDHashTable *table, PLDHashEntryHdr *entry); + + static void Finalize( PLDHashTable *table); + + static + PLDHashOperator FreeCacheEntries(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg); + static + PLDHashOperator VisitEntry(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg); + + // member variables + static PLDHashTableOps ops; + PLDHashTable table; + PRBool initialized; +}; + +#endif // _nsCacheEntry_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheEntryDescriptor.cpp @@ -0,0 +1,675 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheEntryDescriptor.cpp, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan, 22-February-2001 + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsICache.h" +#include "nsCache.h" +#include "nsCacheService.h" +#include "nsCacheEntryDescriptor.h" +#include "nsCacheEntry.h" +#include "nsReadableUtils.h" +#include "nsIOutputStream.h" +#include "nsCRT.h" + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsCacheEntryDescriptor, + nsICacheEntryDescriptor, + nsICacheEntryInfo) + +nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry, + nsCacheAccessMode accessGranted) + : mCacheEntry(entry), + mAccessGranted(accessGranted) +{ + PR_INIT_CLIST(this); + NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor +} + + +nsCacheEntryDescriptor::~nsCacheEntryDescriptor() +{ + // No need to close if the cache entry has already been severed. This + // helps avoid a shutdown assertion (bug 285519) that is caused when + // consumers end up holding onto these objects past xpcom-shutdown. It's + // okay for them to do that because the cache service calls our Close + // method during xpcom-shutdown, so we don't need to complain about it. + if (mCacheEntry) + Close(); + + nsCacheService * service = nsCacheService::GlobalInstance(); + NS_RELEASE(service); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetClientID(char ** result) +{ + NS_ENSURE_ARG_POINTER(result); + + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return ClientIDFromCacheKey(*(mCacheEntry->Key()), result); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID) +{ + NS_ENSURE_ARG_POINTER(aDeviceID); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + const char* deviceID = mCacheEntry->GetDeviceID(); + if (!deviceID) { + *aDeviceID = nsnull; + return NS_OK; + } + + *aDeviceID = NS_strdup(deviceID); + return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetKey(nsACString &result) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetFetchCount(PRInt32 *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->FetchCount(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetLastFetched(PRUint32 *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->LastFetched(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetLastModified(PRUint32 *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->LastModified(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetExpirationTime(PRUint32 *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->ExpirationTime(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetExpirationTime(PRUint32 expirationTime) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + mCacheEntry->SetExpirationTime(expirationTime); + mCacheEntry->MarkEntryDirty(); + return NS_OK; +} + + +NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(PRBool *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->IsStreamData(); + return NS_OK; +} + + +NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(PRUint32 *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->DataSize(); + return NS_OK; +} + + +nsresult +nsCacheEntryDescriptor::RequestDataSizeChange(PRInt32 deltaSize) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + nsresult rv; + rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize); + if (NS_SUCCEEDED(rv)) { + // XXX review for signed/unsigned math errors + PRUint32 newDataSize = mCacheEntry->DataSize() + deltaSize; + mCacheEntry->SetDataSize(newDataSize); + mCacheEntry->TouchData(); + } + return rv; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetDataSize(PRUint32 dataSize) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + // XXX review for signed/unsigned math errors + PRInt32 deltaSize = dataSize - mCacheEntry->DataSize(); + + nsresult rv; + rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize); + // this had better be NS_OK, this call instance is advisory for memory cache objects + if (NS_SUCCEEDED(rv)) { + // XXX review for signed/unsigned math errors + PRUint32 newDataSize = mCacheEntry->DataSize() + deltaSize; + mCacheEntry->SetDataSize(newDataSize); + mCacheEntry->TouchData(); + } else { + NS_WARNING("failed SetDataSize() on memory cache object!"); + } + + return rv; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::OpenInputStream(PRUint32 offset, nsIInputStream ** result) +{ + NS_ENSURE_ARG_POINTER(result); + + { + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; + + // ensure valid permissions + if (!(mAccessGranted & nsICache::ACCESS_READ)) + return NS_ERROR_CACHE_READ_ACCESS_DENIED; + } + + nsInputStreamWrapper* cacheInput = + new nsInputStreamWrapper(this, offset); + if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*result = cacheInput); + return NS_OK; +} + +NS_IMETHODIMP +nsCacheEntryDescriptor::OpenOutputStream(PRUint32 offset, nsIOutputStream ** result) +{ + NS_ENSURE_ARG_POINTER(result); + + { + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; + + // ensure valid permissions + if (!(mAccessGranted & nsICache::ACCESS_WRITE)) + return NS_ERROR_CACHE_WRITE_ACCESS_DENIED; + } + + nsOutputStreamWrapper* cacheOutput = + new nsOutputStreamWrapper(this, offset); + if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*result = cacheOutput); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM; + + NS_IF_ADDREF(*result = mCacheEntry->Data()); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetCacheElement(nsISupports * cacheElement) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM; + + return nsCacheService::SetCacheElement(mCacheEntry, cacheElement); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = mAccessGranted; + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->StoragePolicy(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + // XXX validate policy against session? + + PRBool storageEnabled = PR_FALSE; + storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy); + if (!storageEnabled) return NS_ERROR_FAILURE; + + // Don't change the storage policy of entries we can't write + if (!(mAccessGranted & nsICache::ACCESS_WRITE)) + return NS_ERROR_NOT_AVAILABLE; + + // Don't allow a cache entry to move from memory-only to anything else + if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY && + policy != nsICache::STORE_IN_MEMORY) + return NS_ERROR_NOT_AVAILABLE; + + mCacheEntry->SetStoragePolicy(policy); + mCacheEntry->MarkEntryDirty(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetFile(nsIFile ** result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return nsCacheService::GetFileForEntry(mCacheEntry, result); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetSecurityInfo(nsISupports ** result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + *result = mCacheEntry->SecurityInfo(); + NS_IF_ADDREF(*result); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetSecurityInfo(nsISupports * securityInfo) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + mCacheEntry->SetSecurityInfo(securityInfo); + mCacheEntry->MarkEntryDirty(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::Doom() +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return nsCacheService::DoomEntry(mCacheEntry); +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status) +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::MarkValid() +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = nsCacheService::ValidateEntry(mCacheEntry); + return rv; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::Close() +{ + nsCacheServiceAutoLock lock; + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + // XXX perhaps closing descriptors should clear/sever transports + + // tell nsCacheService we're going away + nsCacheService::CloseDescriptor(this); + NS_ASSERTION(mCacheEntry == nsnull, "mCacheEntry not null"); + + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::GetMetaDataElement(const char *key, char **result) +{ + NS_ENSURE_ARG_POINTER(key); + *result = nsnull; + + nsCacheServiceAutoLock lock; + NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE); + + const char *value; + + value = mCacheEntry->GetMetaDataElement(key); + if (!value) return NS_ERROR_NOT_AVAILABLE; + + *result = NS_strdup(value); + if (!*result) return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::SetMetaDataElement(const char *key, const char *value) +{ + NS_ENSURE_ARG_POINTER(key); + + nsCacheServiceAutoLock lock; + NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE); + + // XXX allow null value, for clearing key? + + nsresult rv = mCacheEntry->SetMetaDataElement(key, value); + if (NS_SUCCEEDED(rv)) + mCacheEntry->TouchMetaData(); + return rv; +} + + +NS_IMETHODIMP +nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor * visitor) +{ + nsCacheServiceAutoLock lock; // XXX check callers, we're calling out of module + NS_ENSURE_ARG_POINTER(visitor); + if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; + + return mCacheEntry->VisitMetaDataElements(visitor); +} + + +/****************************************************************************** + * nsCacheInputStream - a wrapper for nsIInputstream keeps the cache entry + * open while referenced. + ******************************************************************************/ + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsInputStreamWrapper, + nsIInputStream) + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::LazyInit() +{ + nsCacheServiceAutoLock lock; + + nsCacheAccessMode mode; + nsresult rv = mDescriptor->GetAccessGranted(&mode); + if (NS_FAILED(rv)) return rv; + + NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED); + + nsCacheEntry* cacheEntry = mDescriptor->CacheEntry(); + if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr<nsIInputStream> input; + rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode, + mStartOffset, + getter_AddRefs(mInput)); + if (NS_FAILED(rv)) return rv; + + mInitialized = PR_TRUE; + return NS_OK; +} + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::Close() +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + return mInput->Close(); +} + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::Available(PRUint32 *avail) +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + return mInput->Available(avail); +} + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::Read(char *buf, PRUint32 count, PRUint32 *countRead) +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + return mInput->Read(buf, count, countRead); +} + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure, + PRUint32 count, PRUint32 *countRead) +{ + // cache stream not buffered + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsCacheEntryDescriptor:: +nsInputStreamWrapper::IsNonBlocking(PRBool *result) +{ + // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK + *result = PR_FALSE; + return NS_OK; +} + + +/****************************************************************************** + * nsCacheOutputStream - a wrapper for nsIOutputstream to track the amount of + * data written to a cache entry. + * - also keeps the cache entry open while referenced. + ******************************************************************************/ + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsOutputStreamWrapper, + nsIOutputStream) + +nsresult nsCacheEntryDescriptor:: +nsOutputStreamWrapper::LazyInit() +{ + nsCacheServiceAutoLock lock; + + nsCacheAccessMode mode; + nsresult rv = mDescriptor->GetAccessGranted(&mode); + if (NS_FAILED(rv)) return rv; + + NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED); + + nsCacheEntry* cacheEntry = mDescriptor->CacheEntry(); + if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE; + + rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset, + getter_AddRefs(mOutput)); + if (NS_FAILED(rv)) return rv; + + nsCacheDevice* device = cacheEntry->CacheDevice(); + if (!device) return NS_ERROR_NOT_AVAILABLE; + + // the entry has been truncated to mStartOffset bytes, inform the device. + PRInt32 size = cacheEntry->DataSize(); + rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size); + if (NS_FAILED(rv)) return rv; + + cacheEntry->SetDataSize(mStartOffset); + + mInitialized = PR_TRUE; + return NS_OK; +} + +nsresult nsCacheEntryDescriptor:: +nsOutputStreamWrapper::OnWrite(PRUint32 count) +{ + if (count > PR_INT32_MAX) return NS_ERROR_UNEXPECTED; + return mDescriptor->RequestDataSizeChange((PRInt32)count); +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::Close() +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + return mOutput->Close(); +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::Flush() +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + return mOutput->Flush(); +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::Write(const char * buf, + PRUint32 count, + PRUint32 * result) +{ + nsresult rv = EnsureInit(); + if (NS_FAILED(rv)) return rv; + + rv = OnWrite(count); + if (NS_FAILED(rv)) return rv; + + return mOutput->Write(buf, count, result); +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::WriteFrom(nsIInputStream * inStr, + PRUint32 count, + PRUint32 * result) +{ + NS_NOTREACHED("cache stream not buffered"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::WriteSegments(nsReadSegmentFun reader, + void * closure, + PRUint32 count, + PRUint32 * result) +{ + NS_NOTREACHED("cache stream not buffered"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCacheEntryDescriptor:: +nsOutputStreamWrapper::IsNonBlocking(PRBool *result) +{ + // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK + *result = PR_FALSE; + return NS_OK; +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheEntryDescriptor.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheEntryDescriptor.h, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan, 22-February-2001 + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#ifndef _nsCacheEntryDescriptor_h_ +#define _nsCacheEntryDescriptor_h_ + +#include "nsICacheEntryDescriptor.h" +#include "nsCacheEntry.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" + +/****************************************************************************** +* nsCacheEntryDescriptor +*******************************************************************************/ +class nsCacheEntryDescriptor : + public PRCList, + public nsICacheEntryDescriptor +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYDESCRIPTOR + NS_DECL_NSICACHEENTRYINFO + + nsCacheEntryDescriptor(nsCacheEntry * entry, nsCacheAccessMode mode); + virtual ~nsCacheEntryDescriptor(); + + /** + * utility method to attempt changing data size of associated entry + */ + nsresult RequestDataSizeChange(PRInt32 deltaSize); + + /** + * methods callbacks for nsCacheService + */ + nsCacheEntry * CacheEntry(void) { return mCacheEntry; } + void ClearCacheEntry(void) { mCacheEntry = nsnull; } + +private: + + + /************************************************************************* + * input stream wrapper class - + * + * The input stream wrapper references the descriptor, but the descriptor + * doesn't need any references to the stream wrapper. + *************************************************************************/ + class nsInputStreamWrapper : public nsIInputStream { + private: + nsCacheEntryDescriptor * mDescriptor; + nsCOMPtr<nsIInputStream> mInput; + PRUint32 mStartOffset; + PRBool mInitialized; + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + nsInputStreamWrapper(nsCacheEntryDescriptor * desc, PRUint32 off) + : mDescriptor(desc) + , mStartOffset(off) + , mInitialized(PR_FALSE) + { + NS_ADDREF(mDescriptor); + } + virtual ~nsInputStreamWrapper() + { + NS_RELEASE(mDescriptor); + } + + private: + nsresult LazyInit(); + nsresult EnsureInit() { return mInitialized ? NS_OK : LazyInit(); } + }; + friend class nsInputStreamWrapper; + + + /************************************************************************* + * output stream wrapper class - + * + * The output stream wrapper references the descriptor, but the descriptor + * doesn't need any references to the stream wrapper. + *************************************************************************/ + class nsOutputStreamWrapper : public nsIOutputStream { + private: + nsCacheEntryDescriptor * mDescriptor; + nsCOMPtr<nsIOutputStream> mOutput; + PRUint32 mStartOffset; + PRBool mInitialized; + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsOutputStreamWrapper(nsCacheEntryDescriptor * desc, PRUint32 off) + : mDescriptor(desc) + , mStartOffset(off) + , mInitialized(PR_FALSE) + { + NS_ADDREF(mDescriptor); // owning ref + } + virtual ~nsOutputStreamWrapper() + { + // XXX _HACK_ the storage stream needs this! + Close(); + NS_RELEASE(mDescriptor); + } + + private: + nsresult LazyInit(); + nsresult EnsureInit() { return mInitialized ? NS_OK : LazyInit(); } + nsresult OnWrite(PRUint32 count); + }; + friend class nsOutputStreamWrapper; + + private: + /** + * nsCacheEntryDescriptor data members + */ + nsCacheEntry * mCacheEntry; // we are a child of the entry + nsCacheAccessMode mAccessGranted; +}; + + +#endif // _nsCacheEntryDescriptor_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheMetaData.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheMetaData.cpp, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCacheMetaData.h" +#include "nsICacheEntryDescriptor.h" +#include "prmem.h" + +const char * +nsCacheMetaData::GetElement(const char * key) +{ + const char * data = mBuffer; + const char * limit = mBuffer + mMetaSize; + + while (data < limit) { + // Point to the value part + const char * value = data + strlen(data) + 1; + if (strcmp(data, key) == 0) + return value; + + // Skip value part + data = value + strlen(value) + 1; + } + return nsnull; +} + + +nsresult +nsCacheMetaData::SetElement(const char * key, + const char * value) +{ + const PRUint32 keySize = strlen(key) + 1; + char * pos = (char *)GetElement(key); + + if (!value) { + // No value means remove the key/value pair completely, if existing + if (pos) { + PRUint32 oldValueSize = strlen(pos) + 1; + PRUint32 offset = pos - mBuffer; + PRUint32 remainder = mMetaSize - (offset + oldValueSize); + + memmove(pos - keySize, pos + oldValueSize, remainder); + mMetaSize -= keySize + oldValueSize; + } + return NS_OK; + } + + const PRUint32 valueSize = strlen(value) + 1; + PRUint32 newSize = mMetaSize + valueSize; + if (pos) { + const PRUint32 oldValueSize = strlen(pos) + 1; + const PRUint32 offset = pos - mBuffer; + const PRUint32 remainder = mMetaSize - (offset + oldValueSize); + + // Update the value in place + newSize -= oldValueSize; + nsresult rv = EnsureBuffer(newSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Move the remainder to the right place + pos = mBuffer + offset; + memmove(pos + valueSize, pos + oldValueSize, remainder); + } else { + // allocate new meta data element + newSize += keySize; + nsresult rv = EnsureBuffer(newSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Add after last element + pos = mBuffer + mMetaSize; + memcpy(pos, key, keySize); + pos += keySize; + } + + // Update value + memcpy(pos, value, valueSize); + mMetaSize = newSize; + + return NS_OK; +} + +nsresult +nsCacheMetaData::FlattenMetaData(char * buffer, PRUint32 bufSize) +{ + if (mMetaSize > bufSize) { + NS_ERROR("buffer size too small for meta data."); + return NS_ERROR_OUT_OF_MEMORY; + } + + memcpy(buffer, mBuffer, mMetaSize); + return NS_OK; +} + +nsresult +nsCacheMetaData::UnflattenMetaData(const char * data, PRUint32 size) +{ + if (data && size) { + nsresult rv = EnsureBuffer(size); + NS_ENSURE_SUCCESS(rv, rv); + + memcpy(mBuffer, data, size); + mMetaSize = size; + } + return NS_OK; +} + +nsresult +nsCacheMetaData::VisitElements(nsICacheMetaDataVisitor * visitor) +{ + const char * data = mBuffer; + const char * limit = mBuffer + mMetaSize; + + while (data < limit) { + const char * key = data; + // Skip key part + data += strlen(data) + 1; + PRBool keepGoing; + nsresult rv = visitor->VisitMetaDataElement(key, data, &keepGoing); + if (NS_FAILED(rv) || !keepGoing) + break; + + // Skip value part + data += strlen(data) + 1; + } + return NS_OK; +} + +nsresult +nsCacheMetaData::EnsureBuffer(PRUint32 bufSize) +{ + if (mBufferSize < bufSize) { + char * buf = (char *)PR_REALLOC(mBuffer, bufSize); + if (!buf) { + return NS_ERROR_OUT_OF_MEMORY; + } + mBuffer = buf; + mBufferSize = bufSize; + } + return NS_OK; +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheMetaData.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheMetaData.h, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsCacheMetaData_h_ +#define _nsCacheMetaData_h_ + +#include "nspr.h" +#include "nscore.h" + +class nsICacheMetaDataVisitor; + +class nsCacheMetaData { +public: + nsCacheMetaData() : mBuffer(nsnull), mBufferSize(0), mMetaSize(0) { } + + ~nsCacheMetaData() { + mBufferSize = mMetaSize = 0; + PR_FREEIF(mBuffer); + } + + const char * GetElement(const char * key); + + nsresult SetElement(const char * key, const char * value); + + PRUint32 Size(void) { return mMetaSize; } + + nsresult FlattenMetaData(char * buffer, PRUint32 bufSize); + + nsresult UnflattenMetaData(const char * buffer, PRUint32 bufSize); + + nsresult VisitElements(nsICacheMetaDataVisitor * visitor); + +private: + nsresult EnsureBuffer(PRUint32 size); + + char * mBuffer; + PRUint32 mBufferSize; + PRUint32 mMetaSize; +}; + +#endif // _nsCacheMetaData_h
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheRequest.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheRequest.h, released + * February 22, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan, 22-February-2001 + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsCacheRequest_h_ +#define _nsCacheRequest_h_ + +#include "nspr.h" +#include "nsCOMPtr.h" +#include "nsICache.h" +#include "nsICacheListener.h" +#include "nsCacheSession.h" +#include "nsCacheService.h" + + +class nsCacheRequest : public PRCList +{ +private: + friend class nsCacheService; + friend class nsCacheEntry; + + nsCacheRequest( nsCString * key, + nsICacheListener * listener, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsCacheSession * session) + : mKey(key), + mInfo(0), + mListener(listener), + mLock(nsnull), + mCondVar(nsnull) + { + MOZ_COUNT_CTOR(nsCacheRequest); + PR_INIT_CLIST(this); + SetAccessRequested(accessRequested); + SetStoragePolicy(session->StoragePolicy()); + if (session->IsStreamBased()) MarkStreamBased(); + if (session->WillDoomEntriesIfExpired()) MarkDoomEntriesIfExpired(); + if (blockingMode == nsICache::BLOCKING) MarkBlockingMode(); + MarkWaitingForValidation(); + NS_IF_ADDREF(mListener); + } + + ~nsCacheRequest() + { + MOZ_COUNT_DTOR(nsCacheRequest); + delete mKey; + if (mLock) PR_DestroyLock(mLock); + if (mCondVar) PR_DestroyCondVar(mCondVar); + NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "request still on a list"); + + if (mListener) + nsCacheService::ReleaseObject_Locked(mListener, mThread); + } + + /** + * Simple Accessors + */ + enum CacheRequestInfo { + eStoragePolicyMask = 0x000000FF, + eStreamBasedMask = 0x00000100, + eDoomEntriesIfExpiredMask = 0x00001000, + eBlockingModeMask = 0x00010000, + eWaitingForValidationMask = 0x00100000, + eAccessRequestedMask = 0xFF000000 + }; + + void SetAccessRequested(nsCacheAccessMode mode) + { + NS_ASSERTION(mode <= 0xFF, "too many bits in nsCacheAccessMode"); + mInfo &= ~eAccessRequestedMask; + mInfo |= mode << 24; + } + + nsCacheAccessMode AccessRequested() + { + return (nsCacheAccessMode)((mInfo >> 24) & 0xFF); + } + + void MarkStreamBased() { mInfo |= eStreamBasedMask; } + PRBool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; } + + + void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; } + PRBool WillDoomEntriesIfExpired() { return (0 != (mInfo & eDoomEntriesIfExpiredMask)); } + + void MarkBlockingMode() { mInfo |= eBlockingModeMask; } + PRBool IsBlocking() { return (0 != (mInfo & eBlockingModeMask)); } + PRBool IsNonBlocking() { return !(mInfo & eBlockingModeMask); } + + void SetStoragePolicy(nsCacheStoragePolicy policy) + { + NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy"); + mInfo &= ~eStoragePolicyMask; // clear storage policy bits + mInfo |= policy; // or in new bits + } + + nsCacheStoragePolicy StoragePolicy() + { + return (nsCacheStoragePolicy)(mInfo & 0xFF); + } + + void MarkWaitingForValidation() { mInfo |= eWaitingForValidationMask; } + void DoneWaitingForValidation() { mInfo &= ~eWaitingForValidationMask; } + PRBool WaitingForValidation() + { + return (mInfo & eWaitingForValidationMask) != 0; + } + + nsresult + WaitForValidation(void) + { + if (!WaitingForValidation()) { // flag already cleared + MarkWaitingForValidation(); // set up for next time + return NS_OK; // early exit; + } + + if (!mLock) { + mLock = PR_NewLock(); + if (!mLock) return NS_ERROR_OUT_OF_MEMORY; + + NS_ASSERTION(!mCondVar,"we have mCondVar, but didn't have mLock?"); + mCondVar = PR_NewCondVar(mLock); + if (!mCondVar) { + PR_DestroyLock(mLock); + return NS_ERROR_OUT_OF_MEMORY; + } + } + PRStatus status = PR_SUCCESS; + PR_Lock(mLock); + while (WaitingForValidation() && (status == PR_SUCCESS) ) { + status = PR_WaitCondVar(mCondVar, PR_INTERVAL_NO_TIMEOUT); + } + MarkWaitingForValidation(); // set up for next time + PR_Unlock(mLock); + + NS_ASSERTION(status == PR_SUCCESS, "PR_WaitCondVar() returned PR_FAILURE?"); + if (status == PR_FAILURE) + return NS_ERROR_UNEXPECTED; + + return NS_OK; + } + + void WakeUp(void) { + DoneWaitingForValidation(); + if (mLock) { + PR_Lock(mLock); + PR_NotifyCondVar(mCondVar); + PR_Unlock(mLock); + } + } + + /** + * Data members + */ + nsCString * mKey; + PRUint32 mInfo; + nsICacheListener * mListener; // strong ref + nsCOMPtr<nsIThread> mThread; + PRLock * mLock; + PRCondVar * mCondVar; +}; + +#endif // _nsCacheRequest_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheService.cpp @@ -0,0 +1,2089 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheService.cpp, released + * February 10, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan, 10-February-2001 + * Michael Ventnor <m.ventnor@gmail.com> + * Ehsan Akhgari <ehsan.akhgari@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "necko-config.h" + +#include "nsCache.h" +#include "nsCacheService.h" +#include "nsCacheRequest.h" +#include "nsCacheEntry.h" +#include "nsCacheEntryDescriptor.h" +#include "nsCacheDevice.h" +#include "nsMemoryCacheDevice.h" +#include "nsICacheVisitor.h" +#include "nsDiskCacheDevice.h" + +#ifdef NECKO_OFFLINE_CACHE +#include "nsDiskCacheDeviceSQL.h" +#endif + +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefBranch2.h" +#include "nsILocalFile.h" +#include "nsIOService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "nsVoidArray.h" +#include "nsDeleteDir.h" +#include "nsIPrivateBrowsingService.h" +#include "nsNetCID.h" +#include <math.h> // for log() +#include "mozilla/Services.h" + +#include "mozilla/FunctionTimer.h" + +/****************************************************************************** + * nsCacheProfilePrefObserver + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark nsCacheProfilePrefObserver +#endif + +#define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable" +#define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory" +#define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity" +#define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size" +#define DISK_CACHE_CAPACITY 51200 + +#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable" +#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory" +#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity" +#define OFFLINE_CACHE_CAPACITY 512000 + +#define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable" +#define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity" +#define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size" + +static const char * observerList[] = { + "profile-before-change", + "profile-after-change", + NS_XPCOM_SHUTDOWN_OBSERVER_ID, + NS_PRIVATE_BROWSING_SWITCH_TOPIC +}; +static const char * prefList[] = { +#ifdef NECKO_DISK_CACHE + DISK_CACHE_ENABLE_PREF, + DISK_CACHE_CAPACITY_PREF, + DISK_CACHE_DIR_PREF, +#endif +#ifdef NECKO_OFFLINE_CACHE + OFFLINE_CACHE_ENABLE_PREF, + OFFLINE_CACHE_CAPACITY_PREF, + OFFLINE_CACHE_DIR_PREF, +#endif + MEMORY_CACHE_ENABLE_PREF, + MEMORY_CACHE_CAPACITY_PREF +}; + +class nsCacheProfilePrefObserver : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsCacheProfilePrefObserver() + : mHaveProfile(PR_FALSE) + , mDiskCacheEnabled(PR_FALSE) + , mDiskCacheCapacity(0) + , mOfflineCacheEnabled(PR_FALSE) + , mOfflineCacheCapacity(0) + , mMemoryCacheEnabled(PR_TRUE) + , mMemoryCacheCapacity(-1) + , mInPrivateBrowsing(PR_FALSE) + { + } + + virtual ~nsCacheProfilePrefObserver() {} + + nsresult Install(); + void Remove(); + nsresult ReadPrefs(nsIPrefBranch* branch); + + PRBool DiskCacheEnabled(); + PRInt32 DiskCacheCapacity() { return mDiskCacheCapacity; } + nsILocalFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; } + + PRBool OfflineCacheEnabled(); + PRInt32 OfflineCacheCapacity() { return mOfflineCacheCapacity; } + nsILocalFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; } + + PRBool MemoryCacheEnabled(); + PRInt32 MemoryCacheCapacity(); + +private: + PRBool mHaveProfile; + + PRBool mDiskCacheEnabled; + PRInt32 mDiskCacheCapacity; // in kilobytes + nsCOMPtr<nsILocalFile> mDiskCacheParentDirectory; + + PRBool mOfflineCacheEnabled; + PRInt32 mOfflineCacheCapacity; // in kilobytes + nsCOMPtr<nsILocalFile> mOfflineCacheParentDirectory; + + PRBool mMemoryCacheEnabled; + PRInt32 mMemoryCacheCapacity; // in kilobytes + + PRBool mInPrivateBrowsing; +}; + +NS_IMPL_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver) + + +nsresult +nsCacheProfilePrefObserver::Install() +{ + // install profile-change observer + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + nsresult rv, rv2 = NS_OK; + for (unsigned int i=0; i<NS_ARRAY_LENGTH(observerList); i++) { + rv = observerService->AddObserver(this, observerList[i], PR_FALSE); + if (NS_FAILED(rv)) + rv2 = rv; + } + + // install preferences observer + nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!branch) return NS_ERROR_FAILURE; + + for (unsigned int i=0; i<NS_ARRAY_LENGTH(prefList); i++) { + rv = branch->AddObserver(prefList[i], this, PR_FALSE); + if (NS_FAILED(rv)) + rv2 = rv; + } + + // determine the initial status of the private browsing mode + nsCOMPtr<nsIPrivateBrowsingService> pbs = + do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID); + if (pbs) + pbs->GetPrivateBrowsingEnabled(&mInPrivateBrowsing); + + // Determine if we have a profile already + // Install() is called *after* the profile-after-change notification + // when there is only a single profile, or it is specified on the + // commandline at startup. + // In that case, we detect the presence of a profile by the existence + // of the NS_APP_USER_PROFILE_50_DIR directory. + + nsCOMPtr<nsIFile> directory; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(directory)); + if (NS_SUCCEEDED(rv)) + mHaveProfile = PR_TRUE; + + rv = ReadPrefs(branch); + NS_ENSURE_SUCCESS(rv, rv); + + return rv2; +} + + +void +nsCacheProfilePrefObserver::Remove() +{ + // remove Observer Service observers + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + for (unsigned int i=0; i<NS_ARRAY_LENGTH(observerList); i++) { + obs->RemoveObserver(this, observerList[i]); + } + } + + // remove Pref Service observers + nsCOMPtr<nsIPrefBranch2> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) + return; + for (unsigned int i=0; i<NS_ARRAY_LENGTH(prefList); i++) + prefs->RemoveObserver(prefList[i], this); // remove cache pref observers +} + + +NS_IMETHODIMP +nsCacheProfilePrefObserver::Observe(nsISupports * subject, + const char * topic, + const PRUnichar * data_unicode) +{ + nsresult rv; + NS_ConvertUTF16toUTF8 data(data_unicode); + CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get())); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + // xpcom going away, shutdown cache service + if (nsCacheService::GlobalInstance()) + nsCacheService::GlobalInstance()->Shutdown(); + + } else if (!strcmp("profile-before-change", topic)) { + // profile before change + mHaveProfile = PR_FALSE; + + // XXX shutdown devices + nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse", + data.get())); + + } else if (!strcmp("profile-after-change", topic)) { + // profile after change + mHaveProfile = PR_TRUE; + nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + ReadPrefs(branch); + nsCacheService::OnProfileChanged(); + + } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) { + + // ignore pref changes until we're done switch profiles + if (!mHaveProfile) return NS_OK; + + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv); + if (NS_FAILED(rv)) return rv; + +#ifdef NECKO_DISK_CACHE + // which preference changed? + if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) { + + if (!mInPrivateBrowsing) { + rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, + &mDiskCacheEnabled); + if (NS_FAILED(rv)) return rv; + nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled()); + } + + } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) { + + PRInt32 capacity = 0; + rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity); + if (NS_FAILED(rv)) return rv; + mDiskCacheCapacity = PR_MAX(0, capacity); + nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity); +#if 0 + } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) { + // XXX We probaby don't want to respond to this pref except after + // XXX profile changes. Ideally, there should be somekind of user + // XXX notification that the pref change won't take effect until + // XXX the next time the profile changes (browser launch) +#endif + } else +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + // which preference changed? + if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) { + + if (!mInPrivateBrowsing) { + rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, + &mOfflineCacheEnabled); + if (NS_FAILED(rv)) return rv; + nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled()); + } + + } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) { + + PRInt32 capacity = 0; + rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity); + if (NS_FAILED(rv)) return rv; + mOfflineCacheCapacity = PR_MAX(0, capacity); + nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity); +#if 0 + } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) { + // XXX We probaby don't want to respond to this pref except after + // XXX profile changes. Ideally, there should be some kind of user + // XXX notification that the pref change won't take effect until + // XXX the next time the profile changes (browser launch) +#endif + } else +#endif // !NECKO_OFFLINE_CACHE + + if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) { + + rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, + &mMemoryCacheEnabled); + if (NS_FAILED(rv)) return rv; + nsCacheService::SetMemoryCache(); + + } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) { + + mMemoryCacheCapacity = -1; + (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF, + &mMemoryCacheCapacity); + nsCacheService::SetMemoryCache(); + } + } else if (!strcmp(NS_PRIVATE_BROWSING_SWITCH_TOPIC, topic)) { + if (!strcmp(NS_PRIVATE_BROWSING_ENTER, data.get())) { + mInPrivateBrowsing = PR_TRUE; + + nsCacheService::OnEnterExitPrivateBrowsing(); + +#ifdef NECKO_DISK_CACHE + mDiskCacheEnabled = PR_FALSE; + nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled()); +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + mOfflineCacheEnabled = PR_FALSE; + nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled()); +#endif // !NECKO_OFFLINE_CACHE + } else if (!strcmp(NS_PRIVATE_BROWSING_LEAVE, data.get())) { + mInPrivateBrowsing = PR_FALSE; + + nsCacheService::OnEnterExitPrivateBrowsing(); + +#if defined(NECKO_DISK_CACHE) || defined(NECKO_OFFLINE_CACHE) + nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; +#endif // !NECKO_DISK_CACHE && !NECKO_OFFLINE_CACHE + +#ifdef NECKO_DISK_CACHE + mDiskCacheEnabled = PR_TRUE; // by default enabled + (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, + &mDiskCacheEnabled); + nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled()); +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + mOfflineCacheEnabled = PR_TRUE; // by default enabled + (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, + &mOfflineCacheEnabled); + nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled()); +#endif // !NECKO_OFFLINE_CACHE + } + } + + return NS_OK; +} + + +nsresult +nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch) +{ + nsresult rv = NS_OK; + +#ifdef NECKO_DISK_CACHE + // read disk cache device prefs + if (!mInPrivateBrowsing) { + mDiskCacheEnabled = PR_TRUE; // presume disk cache is enabled + (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled); + } + + mDiskCacheCapacity = DISK_CACHE_CAPACITY; + (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity); + mDiskCacheCapacity = PR_MAX(0, mDiskCacheCapacity); + + (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error + NS_GET_IID(nsILocalFile), + getter_AddRefs(mDiskCacheParentDirectory)); + + if (!mDiskCacheParentDirectory) { + nsCOMPtr<nsIFile> directory; + + // try to get the disk cache parent directory + rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, + getter_AddRefs(directory)); + if (NS_FAILED(rv)) { + // try to get the profile directory (there may not be a profile yet) + nsCOMPtr<nsIFile> profDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profDir)); + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(directory)); + if (!directory) + directory = profDir; + else if (profDir) { + PRBool same; + if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) { + // We no longer store the cache directory in the main + // profile directory, so we should cleanup the old one. + rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache")); + if (NS_SUCCEEDED(rv)) { + PRBool exists; + if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists) + DeleteDir(profDir, PR_FALSE, PR_FALSE); + } + } + } + } + // use file cache in build tree only if asked, to avoid cache dir litter + if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) { + rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, + getter_AddRefs(directory)); + } + if (directory) + mDiskCacheParentDirectory = do_QueryInterface(directory, &rv); + } +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + // read offline cache device prefs + if (!mInPrivateBrowsing) { + mOfflineCacheEnabled = PR_TRUE; // presume offline cache is enabled + (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, + &mOfflineCacheEnabled); + } + + mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY; + (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, + &mOfflineCacheCapacity); + mOfflineCacheCapacity = PR_MAX(0, mOfflineCacheCapacity); + + (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error + NS_GET_IID(nsILocalFile), + getter_AddRefs(mOfflineCacheParentDirectory)); + + if (!mOfflineCacheParentDirectory) { + nsCOMPtr<nsIFile> directory; + + // try to get the offline cache parent directory + rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, + getter_AddRefs(directory)); + if (NS_FAILED(rv)) { + // try to get the profile directory (there may not be a profile yet) + nsCOMPtr<nsIFile> profDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profDir)); + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(directory)); + if (!directory) + directory = profDir; + } +#if DEBUG + if (!directory) { + // use current process directory during development + rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, + getter_AddRefs(directory)); + } +#endif + if (directory) + mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv); + } +#endif // !NECKO_OFFLINE_CACHE + + // read memory cache device prefs + (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled); + + mMemoryCacheCapacity = -1; + (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF, + &mMemoryCacheCapacity); + + return rv; +} + + +PRBool +nsCacheProfilePrefObserver::DiskCacheEnabled() +{ + if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return PR_FALSE; + return mDiskCacheEnabled; +} + + +PRBool +nsCacheProfilePrefObserver::OfflineCacheEnabled() +{ + if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory)) + return PR_FALSE; + + return mOfflineCacheEnabled; +} + + +PRBool +nsCacheProfilePrefObserver::MemoryCacheEnabled() +{ + if (mMemoryCacheCapacity == 0) return PR_FALSE; + return mMemoryCacheEnabled; +} + + +/** + * MemoryCacheCapacity + * + * If the browser.cache.memory.capacity preference is positive, we use that + * value for the amount of memory available for the cache. + * + * If browser.cache.memory.capacity is zero, the memory cache is disabled. + * + * If browser.cache.memory.capacity is negative or not present, we use a + * formula that grows less than linearly with the amount of system memory, + * with an upper limit on the cache size. No matter how much physical RAM is + * present, the default cache size would not exceed 32 MB. This maximum would + * apply only to systems with more than 4 GB of RAM (e.g. terminal servers) + * + * RAM Cache + * --- ----- + * 32 Mb 2 Mb + * 64 Mb 4 Mb + * 128 Mb 6 Mb + * 256 Mb 10 Mb + * 512 Mb 14 Mb + * 1024 Mb 18 Mb + * 2048 Mb 24 Mb + * 4096 Mb 30 Mb + * + * The equation for this is (for cache size C and memory size K (kbytes)): + * x = log2(K) - 14 + * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding) + * if (C > 32) C = 32 + */ + +PRInt32 +nsCacheProfilePrefObserver::MemoryCacheCapacity() +{ + PRInt32 capacity = mMemoryCacheCapacity; + if (capacity >= 0) { + CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity)); + return capacity; + } + + static PRUint64 bytes = PR_GetPhysicalMemorySize(); + CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes)); + + // If getting the physical memory failed, arbitrarily assume + // 32 MB of RAM. We use a low default to have a reasonable + // size on all the devices we support. + if (bytes == 0) + bytes = 32 * 1024 * 1024; + + // Conversion from unsigned int64 to double doesn't work on all platforms. + // We need to truncate the value at LL_MAXINT to make sure we don't + // overflow. + if (LL_CMP(bytes, >, LL_MAXINT)) + bytes = LL_MAXINT; + + PRUint64 kbytes; + LL_SHR(kbytes, bytes, 10); + + double kBytesD; + LL_L2D(kBytesD, (PRInt64) kbytes); + + double x = log(kBytesD)/log(2.0) - 14; + if (x > 0) { + capacity = (PRInt32)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding + if (capacity > 32) + capacity = 32; + capacity *= 1024; + } else { + capacity = 0; + } + + return capacity; +} + +/****************************************************************************** + * nsCacheService + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark nsCacheService +#endif + +nsCacheService * nsCacheService::gService = nsnull; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService) + +nsCacheService::nsCacheService() + : mLock(nsnull), + mInitialized(PR_FALSE), + mEnableMemoryDevice(PR_TRUE), + mEnableDiskDevice(PR_TRUE), + mMemoryDevice(nsnull), + mDiskDevice(nsnull), + mOfflineDevice(nsnull), + mTotalEntries(0), + mCacheHits(0), + mCacheMisses(0), + mMaxKeyLength(0), + mMaxDataSize(0), + mMaxMetaSize(0), + mDeactivateFailures(0), + mDeactivatedUnboundEntries(0) +{ + NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!"); + gService = this; + + // create list of cache devices + PR_INIT_CLIST(&mDoomedEntries); + + // allocate service lock + mLock = PR_NewLock(); + +#if defined(DEBUG) + mLockedThread = nsnull; +#endif +} + +nsCacheService::~nsCacheService() +{ + if (mInitialized) // Shutdown hasn't been called yet. + (void) Shutdown(); + + PR_DestroyLock(mLock); + gService = nsnull; +} + + +nsresult +nsCacheService::Init() +{ + NS_TIME_FUNCTION; + + NS_ASSERTION(!mInitialized, "nsCacheService already initialized."); + if (mInitialized) + return NS_ERROR_ALREADY_INITIALIZED; + + if (mLock == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + + CACHE_LOG_INIT(); + + // initialize hashtable for active cache entries + nsresult rv = mActiveEntries.Init(); + if (NS_FAILED(rv)) return rv; + + // create profile/preference observer + mObserver = new nsCacheProfilePrefObserver(); + if (!mObserver) return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(mObserver); + + mObserver->Install(); + mEnableDiskDevice = mObserver->DiskCacheEnabled(); + mEnableOfflineDevice = mObserver->OfflineCacheEnabled(); + mEnableMemoryDevice = mObserver->MemoryCacheEnabled(); + + mInitialized = PR_TRUE; + return NS_OK; +} + + +void +nsCacheService::Shutdown() +{ + nsCacheServiceAutoLock lock; + NS_ASSERTION(mInitialized, + "can't shutdown nsCacheService unless it has been initialized."); + + if (mInitialized) { + + mInitialized = PR_FALSE; + + mObserver->Remove(); + NS_RELEASE(mObserver); + + // Clear entries + ClearDoomList(); + ClearActiveEntries(); + + // deallocate memory and disk caches + delete mMemoryDevice; + mMemoryDevice = nsnull; + +#ifdef NECKO_DISK_CACHE + delete mDiskDevice; + mDiskDevice = nsnull; +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + NS_IF_RELEASE(mOfflineDevice); +#endif // !NECKO_OFFLINE_CACHE + +#if defined(NECKO_DISK_CACHE) && defined(PR_LOGGING) + LogCacheStatistics(); +#endif + } +} + + +NS_METHOD +nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult) +{ + nsresult rv; + + if (aOuter != nsnull) + return NS_ERROR_NO_AGGREGATION; + + nsCacheService * cacheService = new nsCacheService(); + if (cacheService == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(cacheService); + rv = cacheService->Init(); + if (NS_SUCCEEDED(rv)) { + rv = cacheService->QueryInterface(aIID, aResult); + } + NS_RELEASE(cacheService); + return rv; +} + + +NS_IMETHODIMP +nsCacheService::CreateSession(const char * clientID, + nsCacheStoragePolicy storagePolicy, + PRBool streamBased, + nsICacheSession **result) +{ + *result = nsnull; + + if (this == nsnull) return NS_ERROR_NOT_AVAILABLE; + + nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased); + if (!session) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*result = session); + + return NS_OK; +} + + +nsresult +nsCacheService::EvictEntriesForSession(nsCacheSession * session) +{ + NS_ASSERTION(gService, "nsCacheService::gService is null."); + return gService->EvictEntriesForClient(session->ClientID()->get(), + session->StoragePolicy()); +} + + +nsresult +nsCacheService::EvictEntriesForClient(const char * clientID, + nsCacheStoragePolicy storagePolicy) +{ + if (this == nsnull) return NS_ERROR_NOT_AVAILABLE; // XXX eh? + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + // Proxy to the UI thread since the observer service isn't thredsafe. + // We use an async proxy, since this it's not important whether this + // notification happens before or after the actual eviction. + + nsCOMPtr<nsIObserverService> obsProxy; + NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD, + NS_GET_IID(nsIObserverService), obsSvc, + NS_PROXY_ASYNC, getter_AddRefs(obsProxy)); + + if (obsProxy) { + obsProxy->NotifyObservers(this, + NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, + nsnull); + } + } + + nsCacheServiceAutoLock lock; + nsresult res = NS_OK; + +#ifdef NECKO_DISK_CACHE + if (storagePolicy == nsICache::STORE_ANYWHERE || + storagePolicy == nsICache::STORE_ON_DISK) { + + if (mEnableDiskDevice) { + nsresult rv; + if (!mDiskDevice) + rv = CreateDiskDevice(); + if (mDiskDevice) + rv = mDiskDevice->EvictEntries(clientID); + if (NS_FAILED(rv)) res = rv; + } + } +#endif // ! NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + // Only clear the offline cache if it has been specifically asked for. + if (storagePolicy == nsICache::STORE_OFFLINE) { + if (mEnableOfflineDevice) { + nsresult rv; + if (!mOfflineDevice) + rv = CreateOfflineDevice(); + if (mOfflineDevice) + rv = mOfflineDevice->EvictEntries(clientID); + if (NS_FAILED(rv)) res = rv; + } + } +#endif // ! NECKO_OFFLINE_CACHE + + if (storagePolicy == nsICache::STORE_ANYWHERE || + storagePolicy == nsICache::STORE_IN_MEMORY) { + + // If there is no memory device, there is no need to evict it... + if (mMemoryDevice) { + nsresult rv; + rv = mMemoryDevice->EvictEntries(clientID); + if (NS_FAILED(rv)) res = rv; + } + } + + return res; +} + + +nsresult +nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, + PRBool * result) +{ + if (gService == nsnull) return NS_ERROR_NOT_AVAILABLE; + nsCacheServiceAutoLock lock; + + *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy); + return NS_OK; +} + + +PRBool +nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy) +{ + if (gService->mEnableMemoryDevice && + (storagePolicy == nsICache::STORE_ANYWHERE || + storagePolicy == nsICache::STORE_IN_MEMORY)) { + return PR_TRUE; + } + if (gService->mEnableDiskDevice && + (storagePolicy == nsICache::STORE_ANYWHERE || + storagePolicy == nsICache::STORE_ON_DISK || + storagePolicy == nsICache::STORE_ON_DISK_AS_FILE)) { + return PR_TRUE; + } + if (gService->mEnableOfflineDevice && + storagePolicy == nsICache::STORE_OFFLINE) { + return PR_TRUE; + } + + return PR_FALSE; +} + +NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor) +{ + NS_ENSURE_ARG_POINTER(visitor); + + nsCacheServiceAutoLock lock; + + if (!(mEnableDiskDevice || mEnableMemoryDevice)) + return NS_ERROR_NOT_AVAILABLE; + + // XXX record the fact that a visitation is in progress, + // XXX i.e. keep list of visitors in progress. + + nsresult rv = NS_OK; + // If there is no memory device, there are then also no entries to visit... + if (mMemoryDevice) { + rv = mMemoryDevice->Visit(visitor); + if (NS_FAILED(rv)) return rv; + } + +#ifdef NECKO_DISK_CACHE + if (mEnableDiskDevice) { + if (!mDiskDevice) { + rv = CreateDiskDevice(); + if (NS_FAILED(rv)) return rv; + } + rv = mDiskDevice->Visit(visitor); + if (NS_FAILED(rv)) return rv; + } +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + if (mEnableOfflineDevice) { + if (!mOfflineDevice) { + rv = CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + rv = mOfflineDevice->Visit(visitor); + if (NS_FAILED(rv)) return rv; + } +#endif // !NECKO_OFFLINE_CACHE + + // XXX notify any shutdown process that visitation is complete for THIS visitor. + // XXX keep queue of visitors + + return NS_OK; +} + + +NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy) +{ + return EvictEntriesForClient(nsnull, storagePolicy); +} + +/** + * Internal Methods + */ +nsresult +nsCacheService::CreateDiskDevice() +{ +#ifdef NECKO_DISK_CACHE + if (!mInitialized) return NS_ERROR_NOT_AVAILABLE; + if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE; + if (mDiskDevice) return NS_OK; + + mDiskDevice = new nsDiskCacheDevice; + if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY; + + // set the preferences + mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory()); + mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity()); + + nsresult rv = mDiskDevice->Init(); + if (NS_FAILED(rv)) { +#if DEBUG + printf("###\n"); + printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv); + printf("### - disabling disk cache for this session.\n"); + printf("###\n"); +#endif + mEnableDiskDevice = PR_FALSE; + delete mDiskDevice; + mDiskDevice = nsnull; + } + return rv; +#else // !NECKO_DISK_CACHE + NS_NOTREACHED("nsCacheService::CreateDiskDevice"); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +nsresult +nsCacheService::CreateOfflineDevice() +{ +#ifdef NECKO_OFFLINE_CACHE + CACHE_LOG_ALWAYS(("Creating offline device")); + + if (!mInitialized) return NS_ERROR_NOT_AVAILABLE; + if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE; + if (mOfflineDevice) return NS_OK; + + mOfflineDevice = new nsOfflineCacheDevice; + if (!mOfflineDevice) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(mOfflineDevice); + + // set the preferences + mOfflineDevice->SetCacheParentDirectory( + mObserver->OfflineCacheParentDirectory()); + mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity()); + + nsresult rv = mOfflineDevice->Init(); + if (NS_FAILED(rv)) { + CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv)); + CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n")); + + mEnableOfflineDevice = PR_FALSE; + NS_RELEASE(mOfflineDevice); + } + return rv; +#else // !NECKO_DISK_CACHE + NS_NOTREACHED("nsCacheService::CreateOfflineDevice"); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +nsresult +nsCacheService::CreateMemoryDevice() +{ + if (!mInitialized) return NS_ERROR_NOT_AVAILABLE; + if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE; + if (mMemoryDevice) return NS_OK; + + mMemoryDevice = new nsMemoryCacheDevice; + if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY; + + // set preference + PRInt32 capacity = mObserver->MemoryCacheCapacity(); + CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity)); + mMemoryDevice->SetCapacity(capacity); + + nsresult rv = mMemoryDevice->Init(); + if (NS_FAILED(rv)) { + NS_WARNING("Initialization of Memory Cache failed."); + delete mMemoryDevice; + mMemoryDevice = nsnull; + } + return rv; +} + + +nsresult +nsCacheService::CreateRequest(nsCacheSession * session, + const nsACString & clientKey, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsICacheListener * listener, + nsCacheRequest ** request) +{ + NS_ASSERTION(request, "CreateRequest: request is null"); + + nsCString * key = new nsCString(*session->ClientID()); + if (!key) + return NS_ERROR_OUT_OF_MEMORY; + key->Append(':'); + key->Append(clientKey); + + if (mMaxKeyLength < key->Length()) mMaxKeyLength = key->Length(); + + // create request + *request = new nsCacheRequest(key, listener, accessRequested, blockingMode, session); + if (!*request) { + delete key; + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!listener) return NS_OK; // we're sync, we're done. + + // get the request's thread + (*request)->mThread = do_GetCurrentThread(); + + return NS_OK; +} + + +class nsCacheListenerEvent : public nsRunnable +{ +public: + nsCacheListenerEvent(nsICacheListener *listener, + nsICacheEntryDescriptor *descriptor, + nsCacheAccessMode accessGranted, + nsresult status) + : mListener(listener) // transfers reference + , mDescriptor(descriptor) // transfers reference (may be null) + , mAccessGranted(accessGranted) + , mStatus(status) + {} + + NS_IMETHOD Run() + { + mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus); + + NS_RELEASE(mListener); + NS_IF_RELEASE(mDescriptor); + return NS_OK; + } + +private: + // We explicitly leak mListener or mDescriptor if Run is not called + // because otherwise we cannot guarantee that they are destroyed on + // the right thread. + + nsICacheListener *mListener; + nsICacheEntryDescriptor *mDescriptor; + nsCacheAccessMode mAccessGranted; + nsresult mStatus; +}; + + +nsresult +nsCacheService::NotifyListener(nsCacheRequest * request, + nsICacheEntryDescriptor * descriptor, + nsCacheAccessMode accessGranted, + nsresult status) +{ + NS_ASSERTION(request->mThread, "no thread set in async request!"); + + // Swap ownership, and release listener on target thread... + nsICacheListener *listener = request->mListener; + request->mListener = nsnull; + + nsCOMPtr<nsIRunnable> ev = + new nsCacheListenerEvent(listener, descriptor, + accessGranted, status); + if (!ev) { + // Better to leak listener and descriptor if we fail because we don't + // want to destroy them inside the cache service lock or on potentially + // the wrong thread. + return NS_ERROR_OUT_OF_MEMORY; + } + + return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL); +} + + +nsresult +nsCacheService::ProcessRequest(nsCacheRequest * request, + PRBool calledFromOpenCacheEntry, + nsICacheEntryDescriptor ** result) +{ + // !!! must be called with mLock held !!! + nsresult rv; + nsCacheEntry * entry = nsnull; + nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE; + if (result) *result = nsnull; + + while(1) { // Activate entry loop + rv = ActivateEntry(request, &entry); // get the entry for this request + if (NS_FAILED(rv)) break; + + while(1) { // Request Access loop + NS_ASSERTION(entry, "no entry in Request Access loop!"); + // entry->RequestAccess queues request on entry + rv = entry->RequestAccess(request, &accessGranted); + if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break; + + if (request->mListener) // async exits - validate, doom, or close will resume + return rv; + + if (request->IsBlocking()) { + // XXX this is probably wrong... + Unlock(); + rv = request->WaitForValidation(); + Lock(); + } + + PR_REMOVE_AND_INIT_LINK(request); + if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error + // okay, we're ready to process this request, request access again + } + if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break; + + if (entry->IsNotInUse()) { + // this request was the last one keeping it around, so get rid of it + DeactivateEntry(entry); + } + // loop back around to look for another entry + } + + nsICacheEntryDescriptor *descriptor = nsnull; + + if (NS_SUCCEEDED(rv)) + rv = entry->CreateDescriptor(request, accessGranted, &descriptor); + + if (request->mListener) { // Asynchronous + + if (NS_FAILED(rv) && calledFromOpenCacheEntry) + return rv; // skip notifying listener, just return rv to caller + + // call listener to report error or descriptor + nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv); + if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) { + rv = rv2; // trigger delete request + } + } else { // Synchronous + *result = descriptor; + } + return rv; +} + + +nsresult +nsCacheService::OpenCacheEntry(nsCacheSession * session, + const nsACString & key, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsICacheListener * listener, + nsICacheEntryDescriptor ** result) +{ + CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n", + session, PromiseFlatCString(key).get(), accessRequested, + blockingMode)); + NS_ASSERTION(gService, "nsCacheService::gService is null."); + if (result) + *result = nsnull; + + if (!gService->mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsCacheRequest * request = nsnull; + + nsCacheServiceAutoLock lock; + nsresult rv = gService->CreateRequest(session, + key, + accessRequested, + blockingMode, + listener, + &request); + if (NS_FAILED(rv)) return rv; + + CACHE_LOG_DEBUG(("Created request %p\n", request)); + + rv = gService->ProcessRequest(request, PR_TRUE, result); + + // delete requests that have completed + if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))) + delete request; + + return rv; +} + + +nsresult +nsCacheService::ActivateEntry(nsCacheRequest * request, + nsCacheEntry ** result) +{ + CACHE_LOG_DEBUG(("Activate entry for request %p\n", request)); + + nsresult rv = NS_OK; + + NS_ASSERTION(request != nsnull, "ActivateEntry called with no request"); + if (result) *result = nsnull; + if ((!request) || (!result)) return NS_ERROR_NULL_POINTER; + + // check if the request can be satisfied + if (!mEnableMemoryDevice && !request->IsStreamBased()) + return NS_ERROR_FAILURE; + if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy())) + return NS_ERROR_FAILURE; + + // search active entries (including those not bound to device) + nsCacheEntry *entry = mActiveEntries.GetEntry(request->mKey); + CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry)); + + if (!entry) { + // search cache devices for entry + PRBool collision = PR_FALSE; + entry = SearchCacheDevices(request->mKey, request->StoragePolicy(), &collision); + CACHE_LOG_DEBUG(("Device search for request %p returned %p\n", + request, entry)); + // When there is a hashkey collision just refuse to cache it... + if (collision) return NS_ERROR_CACHE_IN_USE; + + if (entry) entry->MarkInitialized(); + } + + if (entry) { + ++mCacheHits; + entry->Fetched(); + } else { + ++mCacheMisses; + } + + if (entry && + ((request->AccessRequested() == nsICache::ACCESS_WRITE) || + ((request->StoragePolicy() != nsICache::STORE_OFFLINE) && + (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) && + request->WillDoomEntriesIfExpired())))) + + { + // this is FORCE-WRITE request or the entry has expired + rv = DoomEntry_Internal(entry); + if (NS_FAILED(rv)) { + // XXX what to do? Increment FailedDooms counter? + } + entry = nsnull; + } + + if (!entry) { + if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) { + // this is a READ-ONLY request + rv = NS_ERROR_CACHE_KEY_NOT_FOUND; + goto error; + } + + entry = new nsCacheEntry(request->mKey, + request->IsStreamBased(), + request->StoragePolicy()); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; + + entry->Fetched(); + ++mTotalEntries; + + // XXX we could perform an early bind in some cases based on storage policy + } + + if (!entry->IsActive()) { + rv = mActiveEntries.AddEntry(entry); + if (NS_FAILED(rv)) goto error; + CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry)); + entry->MarkActive(); // mark entry active, because it's now in mActiveEntries + } + *result = entry; + return NS_OK; + + error: + *result = nsnull; + if (entry) { + delete entry; + } + return rv; +} + + +nsCacheEntry * +nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, PRBool *collision) +{ + nsCacheEntry * entry = nsnull; + + CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice)); + + *collision = PR_FALSE; + if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) { + // If there is no memory device, then there is nothing to search... + if (mMemoryDevice) { + entry = mMemoryDevice->FindEntry(key, collision); + CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, " + "collision: %d\n", key->get(), entry, collision)); + } + } + + if (!entry && + ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) { + +#ifdef NECKO_DISK_CACHE + if (mEnableDiskDevice) { + if (!mDiskDevice) { + nsresult rv = CreateDiskDevice(); + if (NS_FAILED(rv)) + return nsnull; + } + + entry = mDiskDevice->FindEntry(key, collision); + } +#endif // !NECKO_DISK_CACHE + } + + if (!entry && (policy == nsICache::STORE_OFFLINE || + (policy == nsICache::STORE_ANYWHERE && + gIOService->IsOffline()))) { + +#ifdef NECKO_OFFLINE_CACHE + if (mEnableOfflineDevice) { + if (!mOfflineDevice) { + nsresult rv = CreateOfflineDevice(); + if (NS_FAILED(rv)) + return nsnull; + } + + entry = mOfflineDevice->FindEntry(key, collision); + } +#endif // !NECKO_OFFLINE_CACHE + } + + return entry; +} + + +nsCacheDevice * +nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry) +{ + nsCacheDevice * device = entry->CacheDevice(); + if (device) return device; + +#ifdef NECKO_DISK_CACHE + if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) { + // this is the default + if (!mDiskDevice) { + (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead) + } + + if (mDiskDevice) { + entry->MarkBinding(); // enter state of binding + nsresult rv = mDiskDevice->BindEntry(entry); + entry->ClearBinding(); // exit state of binding + if (NS_SUCCEEDED(rv)) + device = mDiskDevice; + } + } +#endif // !NECKO_DISK_CACHE + + // if we can't use mDiskDevice, try mMemoryDevice + if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) { + if (!mMemoryDevice) { + (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead) + } + if (mMemoryDevice) { + entry->MarkBinding(); // enter state of binding + nsresult rv = mMemoryDevice->BindEntry(entry); + entry->ClearBinding(); // exit state of binding + if (NS_SUCCEEDED(rv)) + device = mMemoryDevice; + } + } + +#ifdef NECKO_OFFLINE_CACHE + if (!device && entry->IsStreamData() && + entry->IsAllowedOffline() && mEnableOfflineDevice) { + if (!mOfflineDevice) { + (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead) + } + + if (mOfflineDevice) { + entry->MarkBinding(); + nsresult rv = mOfflineDevice->BindEntry(entry); + entry->ClearBinding(); + if (NS_SUCCEEDED(rv)) + device = mOfflineDevice; + } + } +#endif // ! NECKO_OFFLINE_CACHE + + if (device) + entry->SetCacheDevice(device); + return device; +} + + +nsresult +nsCacheService::DoomEntry(nsCacheEntry * entry) +{ + return gService->DoomEntry_Internal(entry); +} + + +nsresult +nsCacheService::DoomEntry_Internal(nsCacheEntry * entry) +{ + if (entry->IsDoomed()) return NS_OK; + + CACHE_LOG_DEBUG(("Dooming entry %p\n", entry)); + nsresult rv = NS_OK; + entry->MarkDoomed(); + + NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device."); + nsCacheDevice * device = entry->CacheDevice(); + if (device) device->DoomEntry(entry); + + if (entry->IsActive()) { + // remove from active entries + mActiveEntries.RemoveEntry(entry); + CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry)); + entry->MarkInactive(); + } + + // put on doom list to wait for descriptors to close + NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list"); + PR_APPEND_LINK(entry, &mDoomedEntries); + + // tell pending requests to get on with their lives... + rv = ProcessPendingRequests(entry); + + // All requests have been removed, but there may still be open descriptors + if (entry->IsNotInUse()) { + DeactivateEntry(entry); // tell device to get rid of it + } + return rv; +} + + +void +nsCacheService::OnProfileShutdown(PRBool cleanse) +{ + if (!gService) return; + if (!gService->mInitialized) { + // The cache service has been shut down, but someone is still holding + // a reference to it. Ignore this call. + return; + } + nsCacheServiceAutoLock lock; + + gService->DoomActiveEntries(); + gService->ClearDoomList(); + +#ifdef NECKO_DISK_CACHE + if (gService->mDiskDevice && gService->mEnableDiskDevice) { + if (cleanse) + gService->mDiskDevice->EvictEntries(nsnull); + + gService->mDiskDevice->Shutdown(); + } + gService->mEnableDiskDevice = PR_FALSE; +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + if (gService->mOfflineDevice && gService->mEnableOfflineDevice) { + if (cleanse) + gService->mOfflineDevice->EvictEntries(nsnull); + + gService->mOfflineDevice->Shutdown(); + } + gService->mEnableOfflineDevice = PR_FALSE; +#endif // !NECKO_OFFLINE_CACHE + + if (gService->mMemoryDevice) { + // clear memory cache + gService->mMemoryDevice->EvictEntries(nsnull); + } + +} + + +void +nsCacheService::OnProfileChanged() +{ + if (!gService) return; + + CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged")); + + nsCacheServiceAutoLock lock; + + gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled(); + gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled(); + gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled(); + +#ifdef NECKO_DISK_CACHE + if (gService->mDiskDevice) { + gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory()); + gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity()); + + // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false + nsresult rv = gService->mDiskDevice->Init(); + if (NS_FAILED(rv)) { + NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed"); + gService->mEnableDiskDevice = PR_FALSE; + // XXX delete mDiskDevice? + } + } +#endif // !NECKO_DISK_CACHE + +#ifdef NECKO_OFFLINE_CACHE + if (gService->mOfflineDevice) { + gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory()); + gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity()); + + // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false + nsresult rv = gService->mOfflineDevice->Init(); + if (NS_FAILED(rv)) { + NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed"); + gService->mEnableOfflineDevice = PR_FALSE; + // XXX delete mOfflineDevice? + } + } +#endif // !NECKO_OFFLINE_CACHE + + // If memoryDevice exists, reset its size to the new profile + if (gService->mMemoryDevice) { + if (gService->mEnableMemoryDevice) { + // make sure that capacity is reset to the right value + PRInt32 capacity = gService->mObserver->MemoryCacheCapacity(); + CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n", + capacity)); + gService->mMemoryDevice->SetCapacity(capacity); + } else { + // tell memory device to evict everything + CACHE_LOG_DEBUG(("memory device disabled\n")); + gService->mMemoryDevice->SetCapacity(0); + // Don't delete memory device, because some entries may be active still... + } + } +} + + +void +nsCacheService::SetDiskCacheEnabled(PRBool enabled) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + gService->mEnableDiskDevice = enabled; +} + + +void +nsCacheService::SetDiskCacheCapacity(PRInt32 capacity) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + +#ifdef NECKO_DISK_CACHE + if (gService->mDiskDevice) { + gService->mDiskDevice->SetCapacity(capacity); + } +#endif // !NECKO_DISK_CACHE + + gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled(); +} + +void +nsCacheService::SetOfflineCacheEnabled(PRBool enabled) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + gService->mEnableOfflineDevice = enabled; +} + +void +nsCacheService::SetOfflineCacheCapacity(PRInt32 capacity) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + +#ifdef NECKO_OFFLINE_CACHE + if (gService->mOfflineDevice) { + gService->mOfflineDevice->SetCapacity(capacity); + } +#endif // !NECKO_OFFLINE_CACHE + + gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled(); +} + + +void +nsCacheService::SetMemoryCache() +{ + if (!gService) return; + + CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache")); + + nsCacheServiceAutoLock lock; + + gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled(); + + if (gService->mEnableMemoryDevice) { + if (gService->mMemoryDevice) { + PRInt32 capacity = gService->mObserver->MemoryCacheCapacity(); + // make sure that capacity is reset to the right value + CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n", + capacity)); + gService->mMemoryDevice->SetCapacity(capacity); + } + } else { + if (gService->mMemoryDevice) { + // tell memory device to evict everything + CACHE_LOG_DEBUG(("memory device disabled\n")); + gService->mMemoryDevice->SetCapacity(0); + // Don't delete memory device, because some entries may be active still... + } + } +} + + +/****************************************************************************** + * static methods for nsCacheEntryDescriptor + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#endif + +void +nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor) +{ + // ask entry to remove descriptor + nsCacheEntry * entry = descriptor->CacheEntry(); + PRBool stillActive = entry->RemoveDescriptor(descriptor); + nsresult rv = NS_OK; + + if (!entry->IsValid()) { + rv = gService->ProcessPendingRequests(entry); + } + + if (!stillActive) { + gService->DeactivateEntry(entry); + } +} + + +nsresult +nsCacheService::GetFileForEntry(nsCacheEntry * entry, + nsIFile ** result) +{ + nsCacheDevice * device = gService->EnsureEntryHasDevice(entry); + if (!device) return NS_ERROR_UNEXPECTED; + + return device->GetFileForEntry(entry, result); +} + + +nsresult +nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIInputStream ** result) +{ + nsCacheDevice * device = gService->EnsureEntryHasDevice(entry); + if (!device) return NS_ERROR_UNEXPECTED; + + return device->OpenInputStreamForEntry(entry, mode, offset, result); +} + +nsresult +nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIOutputStream ** result) +{ + nsCacheDevice * device = gService->EnsureEntryHasDevice(entry); + if (!device) return NS_ERROR_UNEXPECTED; + + return device->OpenOutputStreamForEntry(entry, mode, offset, result); +} + + +nsresult +nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize) +{ + nsCacheDevice * device = gService->EnsureEntryHasDevice(entry); + if (!device) return NS_ERROR_UNEXPECTED; + + return device->OnDataSizeChange(entry, deltaSize); +} + +void +nsCacheService::Lock() +{ + PR_Lock(gService->mLock); + +#if defined(DEBUG) + gService->mLockedThread = PR_GetCurrentThread(); +#endif +} + +void +nsCacheService::Unlock() +{ + NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(), "oops"); + + nsTArray<nsISupports*> doomed; + doomed.SwapElements(gService->mDoomedObjects); + +#if defined(DEBUG) + gService->mLockedThread = nsnull; +#endif + PR_Unlock(gService->mLock); + + for (PRUint32 i = 0; i < doomed.Length(); ++i) + doomed[i]->Release(); +} + +void +nsCacheService::ReleaseObject_Locked(nsISupports * obj, + nsIEventTarget * target) +{ + NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(), "oops"); + + PRBool isCur; + if (!target || NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur) { + gService->mDoomedObjects.AppendElement(obj); + } else { + NS_ProxyRelease(target, obj); + } +} + + +nsresult +nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element) +{ + entry->SetData(element); + entry->TouchData(); + return NS_OK; +} + + +nsresult +nsCacheService::ValidateEntry(nsCacheEntry * entry) +{ + nsCacheDevice * device = gService->EnsureEntryHasDevice(entry); + if (!device) return NS_ERROR_UNEXPECTED; + + entry->MarkValid(); + nsresult rv = gService->ProcessPendingRequests(entry); + NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed."); + // XXX what else should be done? + + return rv; +} + +#ifdef XP_MAC +#pragma mark - +#endif + + +void +nsCacheService::DeactivateEntry(nsCacheEntry * entry) +{ + CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry)); + nsresult rv = NS_OK; + NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!"); + nsCacheDevice * device = nsnull; + + if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize(); + if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize(); + + if (entry->IsDoomed()) { + // remove from Doomed list + PR_REMOVE_AND_INIT_LINK(entry); + } else if (entry->IsActive()) { + // remove from active entries + mActiveEntries.RemoveEntry(entry); + CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n", + entry)); + entry->MarkInactive(); + + // bind entry if necessary to store meta-data + device = EnsureEntryHasDevice(entry); + if (!device) { + CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active " + "entry %p\n", + entry)); + NS_WARNING("DeactivateEntry: unable to bind active entry\n"); + return; + } + } else { + // if mInitialized == PR_FALSE, + // then we're shutting down and this state is okay. + NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state."); + } + + device = entry->CacheDevice(); + if (device) { + rv = device->DeactivateEntry(entry); + if (NS_FAILED(rv)) { + // increment deactivate failure count + ++mDeactivateFailures; + } + } else { + // increment deactivating unbound entry statistic + ++mDeactivatedUnboundEntries; + delete entry; // because no one else will + } +} + + +nsresult +nsCacheService::ProcessPendingRequests(nsCacheEntry * entry) +{ + nsresult rv = NS_OK; + nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ); + nsCacheRequest * nextRequest; + PRBool newWriter = PR_FALSE; + + if (request == &entry->mRequestQ) return NS_OK; // no queued requests + + if (!entry->IsDoomed() && entry->IsInvalid()) { + // 1st descriptor closed w/o MarkValid() + NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors"); + +#if DEBUG + // verify no ACCESS_WRITE requests(shouldn't have any of these) + while (request != &entry->mRequestQ) { + NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE, + "ACCESS_WRITE request should have been given a new entry"); + request = (nsCacheRequest *)PR_NEXT_LINK(request); + } + request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ); +#endif + // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer + while (request != &entry->mRequestQ) { + if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) { + newWriter = PR_TRUE; + break; + } + + request = (nsCacheRequest *)PR_NEXT_LINK(request); + } + + if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top + request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ); + + // XXX what should we do if there are only READ requests in queue? + // XXX serialize their accesses, give them only read access, but force them to check validate flag? + // XXX or do readers simply presume the entry is valid + } + + nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE; + + while (request != &entry->mRequestQ) { + nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request); + + if (request->mListener) { + + // Async request + PR_REMOVE_AND_INIT_LINK(request); + + if (entry->IsDoomed()) { + rv = ProcessRequest(request, PR_FALSE, nsnull); + if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) + rv = NS_OK; + else + delete request; + + if (NS_FAILED(rv)) { + // XXX what to do? + } + } else if (entry->IsValid() || newWriter) { + rv = entry->RequestAccess(request, &accessGranted); + NS_ASSERTION(NS_SUCCEEDED(rv), + "if entry is valid, RequestAccess must succeed."); + // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?"); + + // entry->CreateDescriptor dequeues request, and queues descriptor + nsICacheEntryDescriptor *descriptor = nsnull; + rv = entry->CreateDescriptor(request, + accessGranted, + &descriptor); + + // post call to listener to report error or descriptor + rv = NotifyListener(request, descriptor, accessGranted, rv); + delete request; + if (NS_FAILED(rv)) { + // XXX what to do? + } + + } else { + // XXX bad state + } + } else { + + // Synchronous request + request->WakeUp(); + } + if (newWriter) break; // process remaining requests after validation + request = nextRequest; + } + + return NS_OK; +} + + +void +nsCacheService::ClearPendingRequests(nsCacheEntry * entry) +{ + nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ); + + while (request != &entry->mRequestQ) { + nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request); + + // XXX we're just dropping these on the floor for now...definitely wrong. + PR_REMOVE_AND_INIT_LINK(request); + delete request; + request = next; + } +} + + +void +nsCacheService::ClearDoomList() +{ + nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries); + + while (entry != &mDoomedEntries) { + nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry); + + entry->DetachDescriptors(); + DeactivateEntry(entry); + entry = next; + } +} + + +void +nsCacheService::ClearActiveEntries() +{ + mActiveEntries.VisitEntries(DeactivateAndClearEntry, nsnull); + mActiveEntries.Shutdown(); +} + + +PLDHashOperator +nsCacheService::DeactivateAndClearEntry(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg) +{ + nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry; + NS_ASSERTION(entry, "### active entry = nsnull!"); + gService->ClearPendingRequests(entry); + entry->DetachDescriptors(); + + entry->MarkInactive(); // so we don't call Remove() while we're enumerating + gService->DeactivateEntry(entry); + + return PL_DHASH_REMOVE; // and continue enumerating +} + + +void +nsCacheService::DoomActiveEntries() +{ + nsAutoTArray<nsCacheEntry*, 8> array; + + mActiveEntries.VisitEntries(RemoveActiveEntry, &array); + + PRUint32 count = array.Length(); + for (PRUint32 i=0; i < count; ++i) + DoomEntry_Internal(array[i]); +} + + +PLDHashOperator +nsCacheService::RemoveActiveEntry(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg) +{ + nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry; + NS_ASSERTION(entry, "### active entry = nsnull!"); + + nsTArray<nsCacheEntry*> * array = (nsTArray<nsCacheEntry*> *) arg; + NS_ASSERTION(array, "### array = nsnull!"); + array->AppendElement(entry); + + // entry is being removed from the active entry list + entry->MarkInactive(); + return PL_DHASH_REMOVE; // and continue enumerating +} + + +#if defined(PR_LOGGING) +void +nsCacheService::LogCacheStatistics() +{ + PRUint32 hitPercentage = (PRUint32)((((double)mCacheHits) / + ((double)(mCacheHits + mCacheMisses))) * 100); + CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n")); + CACHE_LOG_ALWAYS((" TotalEntries = %d\n", mTotalEntries)); + CACHE_LOG_ALWAYS((" Cache Hits = %d\n", mCacheHits)); + CACHE_LOG_ALWAYS((" Cache Misses = %d\n", mCacheMisses)); + CACHE_LOG_ALWAYS((" Cache Hit %% = %d%%\n", hitPercentage)); + CACHE_LOG_ALWAYS((" Max Key Length = %d\n", mMaxKeyLength)); + CACHE_LOG_ALWAYS((" Max Meta Size = %d\n", mMaxMetaSize)); + CACHE_LOG_ALWAYS((" Max Data Size = %d\n", mMaxDataSize)); + CACHE_LOG_ALWAYS(("\n")); + CACHE_LOG_ALWAYS((" Deactivate Failures = %d\n", + mDeactivateFailures)); + CACHE_LOG_ALWAYS((" Deactivated Unbound Entries = %d\n", + mDeactivatedUnboundEntries)); +} +#endif + + +void +nsCacheService::OnEnterExitPrivateBrowsing() +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + + gService->DoomActiveEntries(); + + if (gService->mMemoryDevice) { + // clear memory cache + gService->mMemoryDevice->EvictEntries(nsnull); + } +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheService.h @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheService.h, released + * February 10, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick C. Beard <beard@netscape.com> + * Darin Fisher <darin@netscape.com> + * Ehsan Akhgari <ehsan.akhgari@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#ifndef _nsCacheService_h_ +#define _nsCacheService_h_ + +#include "nsICacheService.h" +#include "nsCacheSession.h" +#include "nsCacheDevice.h" +#include "nsCacheEntry.h" + +#include "prlock.h" +#include "prthread.h" +#include "nsIObserver.h" +#include "nsString.h" +#include "nsProxiedService.h" +#include "nsTArray.h" + +class nsCacheRequest; +class nsCacheProfilePrefObserver; +class nsDiskCacheDevice; +class nsMemoryCacheDevice; +class nsOfflineCacheDevice; +class nsCacheServiceAutoLock; + + +/****************************************************************************** + * nsCacheService + ******************************************************************************/ + +class nsCacheService : public nsICacheService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHESERVICE + + nsCacheService(); + virtual ~nsCacheService(); + + // Define a Create method to be used with a factory: + static NS_METHOD + Create(nsISupports* outer, const nsIID& iid, void* *result); + + + /** + * Methods called by nsCacheSession + */ + static nsresult OpenCacheEntry(nsCacheSession * session, + const nsACString & key, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsICacheListener * listener, + nsICacheEntryDescriptor ** result); + + static nsresult EvictEntriesForSession(nsCacheSession * session); + + static nsresult IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, + PRBool * result); + + /** + * Methods called by nsCacheEntryDescriptor + */ + + static void CloseDescriptor(nsCacheEntryDescriptor * descriptor); + + static nsresult GetFileForEntry(nsCacheEntry * entry, + nsIFile ** result); + + static nsresult OpenInputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIInputStream ** result); + + static nsresult OpenOutputStreamForEntry(nsCacheEntry * entry, + nsCacheAccessMode mode, + PRUint32 offset, + nsIOutputStream ** result); + + static nsresult OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize); + + static nsresult SetCacheElement(nsCacheEntry * entry, nsISupports * element); + + static nsresult ValidateEntry(nsCacheEntry * entry); + + + /** + * Methods called by any cache classes + */ + + static + nsCacheService * GlobalInstance() { return gService; } + + static nsresult DoomEntry(nsCacheEntry * entry); + + static PRBool IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy policy); + + // This method may be called to release an object while the cache service + // lock is being held. If a non-null target is specified and the target + // does not correspond to the current thread, then the release will be + // proxied to the specified target. Otherwise, the object will be added to + // the list of objects to be released when the cache service is unlocked. + static void ReleaseObject_Locked(nsISupports * object, + nsIEventTarget * target = nsnull); + + /** + * Methods called by nsCacheProfilePrefObserver + */ + static void OnProfileShutdown(PRBool cleanse); + static void OnProfileChanged(); + + static void SetDiskCacheEnabled(PRBool enabled); + // Sets the disk cache capacity (in kilobytes) + static void SetDiskCacheCapacity(PRInt32 capacity); + + static void SetOfflineCacheEnabled(PRBool enabled); + // Sets the offline cache capacity (in kilobytes) + static void SetOfflineCacheCapacity(PRInt32 capacity); + + static void SetMemoryCache(); + + static void OnEnterExitPrivateBrowsing(); + + nsresult Init(); + void Shutdown(); +private: + friend class nsCacheServiceAutoLock; + friend class nsOfflineCacheDevice; + + /** + * Internal Methods + */ + + static void Lock(); + static void Unlock(); + + nsresult CreateDiskDevice(); + nsresult CreateOfflineDevice(); + nsresult CreateMemoryDevice(); + + nsresult CreateRequest(nsCacheSession * session, + const nsACString & clientKey, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsICacheListener * listener, + nsCacheRequest ** request); + + nsresult DoomEntry_Internal(nsCacheEntry * entry); + + nsresult EvictEntriesForClient(const char * clientID, + nsCacheStoragePolicy storagePolicy); + + // Notifies request listener asynchronously on the request's thread, and + // releases the descriptor on the request's thread. If this method fails, + // the descriptor is not released. + nsresult NotifyListener(nsCacheRequest * request, + nsICacheEntryDescriptor * descriptor, + nsCacheAccessMode accessGranted, + nsresult error); + + nsresult ActivateEntry(nsCacheRequest * request, nsCacheEntry ** entry); + + nsCacheDevice * EnsureEntryHasDevice(nsCacheEntry * entry); + + nsCacheEntry * SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, PRBool *collision); + + void DeactivateEntry(nsCacheEntry * entry); + + nsresult ProcessRequest(nsCacheRequest * request, + PRBool calledFromOpenCacheEntry, + nsICacheEntryDescriptor ** result); + + nsresult ProcessPendingRequests(nsCacheEntry * entry); + + void ClearPendingRequests(nsCacheEntry * entry); + void ClearDoomList(void); + void ClearActiveEntries(void); + void DoomActiveEntries(void); + + static + PLDHashOperator DeactivateAndClearEntry(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg); + static + PLDHashOperator RemoveActiveEntry(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg); +#if defined(PR_LOGGING) + void LogCacheStatistics(); +#endif + + /** + * Data Members + */ + + static nsCacheService * gService; // there can be only one... + + nsCacheProfilePrefObserver * mObserver; + + PRLock * mLock; + +#if defined(DEBUG) + PRThread * mLockedThread; // The thread holding mLock +#endif + + nsTArray<nsISupports*> mDoomedObjects; + + PRBool mInitialized; + + PRBool mEnableMemoryDevice; + PRBool mEnableDiskDevice; + PRBool mEnableOfflineDevice; + + nsMemoryCacheDevice * mMemoryDevice; + nsDiskCacheDevice * mDiskDevice; + nsOfflineCacheDevice * mOfflineDevice; + + nsCacheEntryHashTable mActiveEntries; + PRCList mDoomedEntries; + + // stats + + PRUint32 mTotalEntries; + PRUint32 mCacheHits; + PRUint32 mCacheMisses; + PRUint32 mMaxKeyLength; + PRUint32 mMaxDataSize; + PRUint32 mMaxMetaSize; + + // Unexpected error totals + PRUint32 mDeactivateFailures; + PRUint32 mDeactivatedUnboundEntries; +}; + +/****************************************************************************** + * nsCacheServiceAutoLock + ******************************************************************************/ + +// Instantiate this class to acquire the cache service lock for a particular +// execution scope. +class nsCacheServiceAutoLock { +public: + nsCacheServiceAutoLock() { + nsCacheService::Lock(); + } + ~nsCacheServiceAutoLock() { + nsCacheService::Unlock(); + } +}; + +#endif // _nsCacheService_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheSession.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheSession.h, released + * February 23, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick Beard <beard@netscape.com> + * Darin Fisher <darin@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCacheSession.h" +#include "nsCacheService.h" +#include "nsCRT.h" + +NS_IMPL_ISUPPORTS1(nsCacheSession, nsICacheSession) + +nsCacheSession::nsCacheSession(const char * clientID, + nsCacheStoragePolicy storagePolicy, + PRBool streamBased) + : mClientID(clientID), + mInfo(0) +{ + SetStoragePolicy(storagePolicy); + + if (streamBased) MarkStreamBased(); + else SetStoragePolicy(nsICache::STORE_IN_MEMORY); + + MarkDoomEntriesIfExpired(); +} + +nsCacheSession::~nsCacheSession() +{ + /* destructor code */ + // notify service we are going away? +} + + +NS_IMETHODIMP nsCacheSession::GetDoomEntriesIfExpired(PRBool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = WillDoomEntriesIfExpired(); + return NS_OK; +} + + +NS_IMETHODIMP nsCacheSession::SetDoomEntriesIfExpired(PRBool doomEntriesIfExpired) +{ + if (doomEntriesIfExpired) MarkDoomEntriesIfExpired(); + else ClearDoomEntriesIfExpired(); + return NS_OK; +} + + +NS_IMETHODIMP +nsCacheSession::OpenCacheEntry(const nsACString & key, + nsCacheAccessMode accessRequested, + PRBool blockingMode, + nsICacheEntryDescriptor ** result) +{ + nsresult rv; + rv = nsCacheService::OpenCacheEntry(this, + key, + accessRequested, + blockingMode, + nsnull, // no listener + result); + return rv; +} + + +NS_IMETHODIMP nsCacheSession::AsyncOpenCacheEntry(const nsACString & key, + nsCacheAccessMode accessRequested, + nsICacheListener *listener) +{ + nsresult rv; + rv = nsCacheService::OpenCacheEntry(this, + key, + accessRequested, + nsICache::BLOCKING, + listener, + nsnull); // no result + + if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) rv = NS_OK; + return rv; +} + +NS_IMETHODIMP nsCacheSession::EvictEntries() +{ + return nsCacheService::EvictEntriesForSession(this); +} + + +NS_IMETHODIMP nsCacheSession::IsStorageEnabled(PRBool *result) +{ + + return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result); +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsCacheSession.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheSession.h, released + * February 23, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick Beard <beard@netscape.com> + * Darin Fisher <darin@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsCacheSession_h_ +#define _nsCacheSession_h_ + +#include "nspr.h" +#include "nsError.h" +#include "nsICacheSession.h" +#include "nsString.h" + +class nsCacheSession : public nsICacheSession +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHESESSION + + nsCacheSession(const char * clientID, nsCacheStoragePolicy storagePolicy, PRBool streamBased); + virtual ~nsCacheSession(); + + nsCString * ClientID() { return &mClientID; } + + enum SessionInfo { + eStoragePolicyMask = 0x000000FF, + eStreamBasedMask = 0x00000100, + eDoomEntriesIfExpiredMask = 0x00001000 + }; + + void MarkStreamBased() { mInfo |= eStreamBasedMask; } + void ClearStreamBased() { mInfo &= ~eStreamBasedMask; } + PRBool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; } + + void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; } + void ClearDoomEntriesIfExpired() { mInfo &= ~eDoomEntriesIfExpiredMask; } + PRBool WillDoomEntriesIfExpired() { return (0 != (mInfo & eDoomEntriesIfExpiredMask)); } + + nsCacheStoragePolicy StoragePolicy() + { + return (nsCacheStoragePolicy)(mInfo & eStoragePolicyMask); + } + + void SetStoragePolicy(nsCacheStoragePolicy policy) + { + NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy"); + mInfo &= ~eStoragePolicyMask; // clear storage policy bits + mInfo |= policy; + } + +private: + nsCString mClientID; + PRUint32 mInfo; +}; + +#endif // _nsCacheSession_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDeleteDir.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@meer.net> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsDeleteDir.h" +#include "nsIFile.h" +#include "nsString.h" +#include "prthread.h" + +static void DeleteDirThreadFunc(void *arg) +{ + nsIFile *dir = static_cast<nsIFile *>(arg); + dir->Remove(PR_TRUE); + NS_RELEASE(dir); +} + +nsresult DeleteDir(nsIFile *dirIn, PRBool moveToTrash, PRBool sync) +{ + nsresult rv; + nsCOMPtr<nsIFile> trash, dir; + + // Need to make a clone of this since we don't want to modify the input + // file object. + rv = dirIn->Clone(getter_AddRefs(dir)); + if (NS_FAILED(rv)) + return rv; + + if (moveToTrash) + { + rv = GetTrashDir(dir, &trash); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> subDir; + rv = trash->Clone(getter_AddRefs(subDir)); + if (NS_FAILED(rv)) + return rv; + + rv = subDir->AppendNative(NS_LITERAL_CSTRING("Trash")); + if (NS_FAILED(rv)) + return rv; + + rv = subDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) + return rv; + + rv = dir->MoveToNative(subDir, EmptyCString()); + if (NS_FAILED(rv)) + return rv; + } + else + { + // we want to pass a clone of the original off to the worker thread. + trash.swap(dir); + } + + // Steal ownership of trash directory; let the thread release it. + nsIFile *trashRef = nsnull; + trash.swap(trashRef); + + if (sync) + { + DeleteDirThreadFunc(trashRef); + } + else + { + // now, invoke the worker thread + PRThread *thread = PR_CreateThread(PR_USER_THREAD, + DeleteDirThreadFunc, + trashRef, + PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + 0); + if (!thread) + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result) +{ + nsresult rv = target->Clone(getter_AddRefs(*result)); + if (NS_FAILED(rv)) + return rv; + + nsCAutoString leaf; + rv = (*result)->GetNativeLeafName(leaf); + if (NS_FAILED(rv)) + return rv; + leaf.AppendLiteral(".Trash"); + + return (*result)->SetNativeLeafName(leaf); +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDeleteDir.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@meer.net> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsDeleteDir_h__ +#define nsDeleteDir_h__ + +#include "nsCOMPtr.h" + +class nsIFile; + +/** + * This routine attempts to delete a directory that may contain some files that + * are still in use. This later point is only an issue on Windows and a few + * other systems. + * + * If the moveToTrash parameter is true, then the process for deleting the + * directory creates a sibling directory of the same name with the ".Trash" + * suffix. It then attempts to move the given directory into the corresponding + * trash folder (moving individual files if necessary). Next, it proceeds to + * delete each file in the trash folder on a low-priority background thread. + * + * If the moveToTrash parameter is false, then the given directory is deleted + * directly. + * + * If the sync flag is true, then the delete operation runs to completion + * before this function returns. Otherwise, deletion occurs asynchronously. + */ +NS_HIDDEN_(nsresult) DeleteDir(nsIFile *dir, PRBool moveToTrash, PRBool sync); + +/** + * This routine returns the trash directory corresponding to the given + * directory. + */ +NS_HIDDEN_(nsresult) GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result); + +#endif // nsDeleteDir_h__
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDiskCache.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsCacheDevice.h, released + * March 9, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Beard <beard@netscape.com> + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#ifndef _nsDiskCache_h_ +#define _nsDiskCache_h_ + +#include "nsCacheEntry.h" + +#ifdef XP_WIN +#include <winsock.h> // for htonl/ntohl +#endif + + +class nsDiskCache { +public: + enum { + kCurrentVersion = 0x0001000C // format = 16 bits major version/16 bits minor version + }; + + enum { kData, kMetaData }; + + // Parameter initval initializes internal state of hash function. Hash values are different + // for the same text when different initval is used. It can be any random number. + // + // It can be used for generating 64-bit hash value: + // (PRUint64(Hash(key, initval1)) << 32) | Hash(key, initval2) + // + // It can be also used to hash multiple strings: + // h = Hash(string1, 0); + // h = Hash(string2, h); + // ... + static PLDHashNumber Hash(const char* key, PLDHashNumber initval=0); + static nsresult Truncate(PRFileDesc * fd, PRUint32 newEOF); +}; + +#endif // _nsDiskCache_h_
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDiskCacheBinding.cpp @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsDiskCacheBinding.cpp, released + * May 10, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick C. Beard <beard@netscape.com> + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <limits.h> + +#include "nscore.h" +#include "nsDiskCacheBinding.h" + + + +/****************************************************************************** + * static hash table callback functions + * + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark HASHTABLE CALLBACKS +#endif + +struct HashTableEntry : PLDHashEntryHdr { + nsDiskCacheBinding * mBinding; +}; + + +static PLDHashNumber +HashKey( PLDHashTable *table, const void *key) +{ + return (PLDHashNumber) NS_PTR_TO_INT32(key); +} + + +static PRBool +MatchEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr * header, + const void * key) +{ + HashTableEntry * hashEntry = (HashTableEntry *) header; + return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key)); +} + +static void +MoveEntry(PLDHashTable * /* table */, + const PLDHashEntryHdr * src, + PLDHashEntryHdr * dst) +{ + ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding; +} + + +static void +ClearEntry(PLDHashTable * /* table */, + PLDHashEntryHdr * header) +{ + ((HashTableEntry *)header)->mBinding = nsnull; +} + + +/****************************************************************************** + * Utility Functions + *****************************************************************************/ +#ifdef XP_MAC +#pragma mark - +#pragma mark DISK CACHE BINDERY +#endif + +nsDiskCacheBinding * +GetCacheEntryBinding(nsCacheEntry * entry) +{ + return (nsDiskCacheBinding *) entry->Data(); +} + + +/****************************************************************************** + * nsDiskCacheBinding + *****************************************************************************/ + +NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheBinding) + +nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record) + : mCacheEntry(entry) + , mStreamIO(nsnull) +{ + NS_ASSERTION(record->ValidRecord(), "bad record"); + PR_INIT_CLIST(this); + mRecord = *record; + mDoomed = entry->IsDoomed(); + mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files +} + +nsDiskCacheBinding::~nsDiskCacheBinding() +{ + NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list"); + if (!PR_CLIST_IS_EMPTY(this)) + PR_REMOVE_LINK(this); // XXX why are we still on a list? + + // sever streamIO/binding link + if (mStreamIO) { + mStreamIO->ClearBinding(); + NS_RELEASE(mStreamIO); + } +} + +nsresult +nsDiskCacheBinding::EnsureStreamIO() +{ + if (!mStreamIO) { + mStreamIO = new nsDiskCacheStreamIO(this); + if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(mStreamIO); + } + return NS_OK; +} + + +/****************************************************************************** + * nsDiskCacheBindery + * + * Keeps track of bound disk cache entries to detect for collisions. + * + *****************************************************************************/ + +PLDHashTableOps nsDiskCacheBindery::ops = +{ + PL_DHashAllocTable, + PL_DHashFreeTable, + HashKey, + MatchEntry, + MoveEntry, + ClearEntry, + PL_DHashFinalizeStub +}; + + +nsDiskCacheBindery::nsDiskCacheBindery() + : initialized(PR_FALSE) +{ +} + + +nsDiskCacheBindery::~nsDiskCacheBindery() +{ + Reset(); +} + + +nsresult +nsDiskCacheBindery::Init() +{ + nsresult rv = NS_OK; + initialized = PL_DHashTableInit(&table, &ops, nsnull, sizeof(HashTableEntry), 0); + + if (!initialized) rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} + +void +nsDiskCacheBindery::Reset() +{ + if (initialized) { + PL_DHashTableFinish(&table); + initialized = PR_FALSE; + } +} + + +nsDiskCacheBinding * +nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry, + nsDiskCacheRecord * record) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + nsCOMPtr<nsISupports> data = entry->Data(); + if (data) { + NS_ERROR("cache entry already has bind data"); + return nsnull; + } + + nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record); + if (!binding) return nsnull; + + // give ownership of the binding to the entry + entry->SetData(binding); + + // add binding to collision detection system + nsresult rv = AddBinding(binding); + if (NS_FAILED(rv)) { + entry->SetData(nsnull); + return nsnull; + } + + return binding; +} + + +/** + * FindActiveEntry : to find active colliding entry so we can doom it + */ +nsDiskCacheBinding * +nsDiskCacheBindery::FindActiveBinding(PRUint32 hashNumber) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + // find hash entry for key + HashTableEntry * hashEntry; + hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, (void*) hashNumber, PL_DHASH_LOOKUP); + if (PL_DHASH_ENTRY_IS_FREE(hashEntry)) return nsnull; + + // walk list looking for active entry + NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding"); + nsDiskCacheBinding * binding = hashEntry->mBinding; + while (binding->mCacheEntry->IsDoomed()) { + binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + if (binding == hashEntry->mBinding) return nsnull; + } + return binding; +} + + +/** + * AddBinding + * + * Called from FindEntry() if we read an entry off of disk + * - it may already have a generation number + * - a generation number conflict is an error + * + * Called from BindEntry() + * - a generation number needs to be assigned + */ +nsresult +nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding) +{ + NS_ENSURE_ARG_POINTER(binding); + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + + // find hash entry for key + HashTableEntry * hashEntry; + hashEntry = (HashTableEntry *) PL_DHashTableOperate(&table, + (void*) binding->mRecord.HashNumber(), + PL_DHASH_ADD); + if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY; + + if (hashEntry->mBinding == nsnull) { + hashEntry->mBinding = binding; + if (binding->mGeneration == 0) + binding->mGeneration = 1; // if generation uninitialized, set it to 1 + + return NS_OK; + } + + + // insert binding in generation order + nsDiskCacheBinding * p = hashEntry->mBinding; + PRBool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation? + if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized + while (1) { + + if (binding->mGeneration < p->mGeneration) { + // here we are + PR_INSERT_BEFORE(binding, p); + if (hashEntry->mBinding == p) + hashEntry->mBinding = binding; + break; + } + + if (binding->mGeneration == p->mGeneration) { + if (calcGeneration) ++binding->mGeneration; // try the next generation + else { + NS_ERROR("### disk cache: generations collide!"); + return NS_ERROR_UNEXPECTED; + } + } + + p = (nsDiskCacheBinding *)PR_NEXT_LINK(p); + if (p == hashEntry->mBinding) { + // end of line: insert here or die + p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation + if (p->mGeneration == 255) { + NS_WARNING("### disk cache: generation capacity at full"); + return NS_ERROR_UNEXPECTED; + } + PR_INSERT_BEFORE(binding, hashEntry->mBinding); + break; + } + } + return NS_OK; +} + + +/** + * RemoveBinding : remove binding from collision detection on deactivation + */ +void +nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding) +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + if (!initialized) return; + + HashTableEntry * hashEntry; + void * key = (void *)binding->mRecord.HashNumber(); + + hashEntry = (HashTableEntry*) PL_DHashTableOperate(&table, + (void*) key, + PL_DHASH_LOOKUP); + if (!PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { + NS_WARNING("### disk cache: binding not in hashtable!"); + return; + } + + if (binding == hashEntry->mBinding) { + if (PR_CLIST_IS_EMPTY(binding)) { + // remove this hash entry + (void) PL_DHashTableOperate(&table, + (void*) binding->mRecord.HashNumber(), + PL_DHASH_REMOVE); + return; + + } else { + // promote next binding to head, and unlink this binding + hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + } + } + PR_REMOVE_AND_INIT_LINK(binding); +} + + +/** + * ActiveBinding : PLDHashTable enumerate function to verify active bindings + */ + +PLDHashOperator +ActiveBinding(PLDHashTable * table, + PLDHashEntryHdr * hdr, + PRUint32 number, + void * arg) +{ + nsDiskCacheBinding * binding = ((HashTableEntry *)hdr)->mBinding; + NS_ASSERTION(binding, "### disk cache binding = nsnull!"); + + nsDiskCacheBinding * head = binding; + do { + if (binding->IsActive()) { + *((PRBool *)arg) = PR_TRUE; + return PL_DHASH_STOP; + } + + binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); + } while (binding != head); + + return PL_DHASH_NEXT; +} + + +/** + * ActiveBindings : return PR_TRUE if any bindings have open descriptors + */ +PRBool +nsDiskCacheBindery::ActiveBindings() +{ + NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); + if (!initialized) return PR_FALSE; + + PRBool activeBinding = PR_FALSE; + PL_DHashTableEnumerate(&table, ActiveBinding, &activeBinding); + + return activeBinding; +}
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDiskCacheBinding.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsDiskCacheBinding.h, released + * May 10, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * Patrick C. Beard <beard@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#ifndef _nsDiskCacheBinding_h_ +#define _nsDiskCacheBinding_h_ + +#include "nspr.h" +#include "pldhash.h" + +#include "nsISupports.h" +#include "nsCacheEntry.h" + +#include "nsDiskCacheMap.h" +#include "nsDiskCacheStreams.h" + + +/****************************************************************************** + * nsDiskCacheBinding + * + * Created for disk cache specific data and stored in nsCacheEntry.mData as + * an nsISupports. Also stored in nsDiskCacheHashTable, with collisions + * linked by the PRCList. + * + *****************************************************************************/ + +class nsDiskCacheBinding : public nsISupports, public PRCList { +public: + NS_DECL_ISUPPORTS + + nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record); + virtual ~nsDiskCacheBinding(); + + nsresult EnsureStreamIO(); + PRBool IsActive() { return mCacheEntry != nsnull;} + +// XXX make friends +public: + nsCacheEntry* mCacheEntry; // back pointer to parent nsCacheEntry + nsDiskCacheRecord mRecord; + nsDiskCacheStreamIO* mStreamIO; // strong reference + PRBool mDoomed; // record is not stored in cache map + PRUint8 mGeneration; // possibly just reservation +}; + + +/****************************************************************************** + * Utility Functions + *****************************************************************************/ + +nsDiskCacheBinding * GetCacheEntryBinding(nsCacheEntry * entry); + + + +/****************************************************************************** + * nsDiskCacheBindery + * + * Used to keep track of nsDiskCacheBinding associated with active/bound (and + * possibly doomed) entries. Lookups on 4 byte disk hash to find collisions + * (which need to be doomed, instead of just evicted. Collisions are linked + * using a PRCList to keep track of current generation number. + * + * Used to detect hash number collisions, and find available generation numbers. + * + * Not all nsDiskCacheBinding have a generation number. + * + * Generation numbers may be aquired late, or lost (when data fits in block file) + * + * Collisions can occur: + * BindEntry() - hashnumbers collide (possibly different keys) + * + * Generation number required: + * DeactivateEntry() - metadata written to disk, may require file + * GetFileForEntry() - force data to require file + * writing to stream - data size may require file + * + * Binding can be kept in PRCList in order of generation numbers. + * Binding with no generation number can be Appended to PRCList (last). + * + *****************************************************************************/ + +class nsDiskCacheBindery { +public: + nsDiskCacheBindery(); + ~nsDiskCacheBindery(); + + nsresult Init(); + void Reset(); + + nsDiskCacheBinding * CreateBinding(nsCacheEntry * entry, + nsDiskCacheRecord * record); + + nsDiskCacheBinding * FindActiveBinding(PRUint32 hashNumber); + void RemoveBinding(nsDiskCacheBinding * binding); + PRBool ActiveBindings(); + +private: + nsresult AddBinding(nsDiskCacheBinding * binding); + + // member variables + static PLDHashTableOps ops; + PLDHashTable table; + PRBool initialized; +}; + +#endif /* _nsDiskCacheBinding_h_ */
new file mode 100644 --- /dev/null +++ b/netwerk/cache/nsDiskCacheBlockFile.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is nsDiskCacheBlockFile.cpp, released + * April 12, 2001. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Gordon Sheridan <gordon@netscape.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsDiskCache.h" +#include "nsDiskCacheBlockFile.h" + +/****************************************************************************** + * nsDiskCacheBlockFile - + *****************************************************************************/ + +const unsigned short kBitMapBytes = 4096; +const unsigned short kBitMapWords = (kBitMapBytes/4); + +/****************************************************************************** + * Open + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::Open( nsILocalFile * blockFile, PRUint32 blockSize) +{ + PRInt32 fileSize; + + mBlockSize = blockSize; + + // open the file - restricted to user, the data could be confidential + nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD); + if (NS_FAILED(rv)) return rv; // unable to open or create file + + // allocate bit map buffer + mBitMap = new PRUint32[kBitMapWords]; + if (!mBitMap) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto error_exit; + } + + // check if we just creating the file + fileSize = PR_Available(mFD); + if (fileSize < 0) { + // XXX an error occurred. We could call PR_GetError(), but how would that help? + rv = NS_ERROR_UNEXPECTED; + goto error_exit; + } + if (fileSize == 0) { + // initialize bit map and write it + memset(mBitMap, 0, kBitMapBytes); + PRInt32 bytesWritten = PR_Write(mFD, mBitMap, kBitMapBytes); + if (bytesWritten < kBitMapBytes) + goto error_exit; + + } else if (fileSize < kBitMapBytes) { + rv = NS_ERROR_UNEXPECTED; // XXX NS_ERROR_CACHE_INVALID; + goto error_exit; + + } else { + // read the bit map + const PRInt32 bytesRead = PR_Read(mFD, mBitMap, kBitMapBytes); + if (bytesRead < kBitMapBytes) { + rv = NS_ERROR_UNEXPECTED; + goto error_exit; + } +#if defined(IS_LITTLE_ENDIAN) + // Swap from network format + for (int i = 0; i < kBitMapWords; ++i) + mBitMap[i] = ntohl(mBitMap[i]); +#endif + // validate block file size + // Because not whole blocks are written, the size may be a + // little bit smaller than used blocks times blocksize, + // because the last block will generally not be 'whole'. + const PRUint32 estimatedSize = CalcBlockFileSize(); + if ((PRUint32)fileSize + blockSize < estimatedSize) { + rv = NS_ERROR_UNEXPECTED; + goto error_exit; + } + } + return NS_OK; + +error_exit: + Close(PR_FALSE); + return rv; +} + + +/****************************************************************************** + * Close + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::Close(PRBool flush) +{ + nsresult rv = NS_OK; + + if (mFD) { + if (flush) + rv = FlushBitMap(); + PRStatus err = PR_Close(mFD); + if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS)) + rv = NS_ERROR_UNEXPECTED; + mFD = nsnull; + } + + if (mBitMap) { + delete [] mBitMap; + mBitMap = nsnull; + } + + return rv; +} + + +/****************************************************************************** + * AllocateBlocks + * + * Allocates 1-4 blocks, using a first fit strategy, + * so that no group of blocks spans a quad block boundary. + * + * Returns block number of first block allocated or -1 on failure. + * + *****************************************************************************/ +PRInt32 +nsDiskCacheBlockFile::AllocateBlocks(PRInt32 numBlocks) +{ + const int maxPos = 32 - numBlocks; + const PRUint32 mask = (0x01 << numBlocks) - 1; + for (int i = 0; i < kBitMapWords; ++i) { + PRUint32 mapWord = ~mBitMap[i]; // flip bits so free bits are 1 + if (mapWord) { // At least one free bit + // Binary search for first free bit in word + int bit = 0; + if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; } + if ((mapWord & 0x000FF) == 0) { bit |= 8; mapWord >>= 8; } + if ((mapWord & 0x0000F) == 0) { bit |= 4; mapWord >>= 4; } + if ((mapWord & 0x00003) == 0) { bit |= 2; mapWord >>= 2; } + if ((mapWord & 0x00001) == 0) { bit |= 1; mapWord >>= 1; } + // Find first fit for mask + for (; bit <= maxPos; ++bit) { + // all bits selected by mask are 1, so free + if ((mask & mapWord) == mask) { + mBitMap[i] |= mask << bit; + mBitMapDirty = PR_TRUE; + return i * 32 + bit; + } + } + } + } + + return -1; +} + + +/****************************************************************************** + * DeallocateBlocks + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::DeallocateBlocks( PRInt32 startBlock, PRInt32 numBlocks) +{ + if (!mFD) return NS_ERROR_NOT_AVAILABLE; + + if ((startBlock < 0) || (startBlock > kBitMapBytes * 8 - 1) || + (numBlocks < 1) || (numBlocks > 4)) + return NS_ERROR_ILLEGAL_VALUE; + + const PRInt32 startWord = startBlock >> 5; // Divide by 32 + const PRUint32 startBit = startBlock & 31; // Modulo by 32 + + // make sure requested deallocation doesn't span a word boundary + if (startBit + numBlocks > 32) return NS_ERROR_UNEXPECTED; + PRUint32 mask = ((0x01 << numBlocks) - 1) << startBit; + + // make sure requested deallocation is currently allocated + if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_ABORT; + + mBitMap[startWord] ^= mask; // flips the bits off; + mBitMapDirty = PR_TRUE; + // XXX rv = FlushBitMap(); // coherency vs. performance + return NS_OK; +} + + +/****************************************************************************** + * WriteBlocks + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::WriteBlocks( void * buffer, + PRUint32 size, + PRInt32 numBlocks, + PRInt32 * startBlock) +{ + // presume buffer != nsnull and startBlock != nsnull + NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE); + + // allocate some blocks in the cache block file + *startBlock = AllocateBlocks(numBlocks); + NS_ENSURE_STATE(*startBlock >= 0); + + // seek to block position + PRInt32 blockPos = kBitMapBytes + *startBlock * mBlockSize; + PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); + NS_ENSURE_STATE(filePos == blockPos); + + // write the blocks + PRInt32 bytesWritten = PR_Write(mFD, buffer, size); + NS_ENSURE_STATE(bytesWritten >= 0 && PRUint32(bytesWritten) == size); + + // write the bit map and flush the file + // XXX except we would take a severe performance hit + // XXX rv = FlushBitMap(); + return NS_OK; +} + + +/****************************************************************************** + * ReadBlocks + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::ReadBlocks( void * buffer, + PRInt32 startBlock, + PRInt32 numBlocks, + PRInt32 * bytesRead) +{ + // presume buffer != nsnull and bytesRead != bytesRead + + if (!mFD) return NS_ERROR_NOT_AVAILABLE; + nsresult rv = VerifyAllocation(startBlock, numBlocks); + if (NS_FAILED(rv)) return rv; + + // seek to block position + PRInt32 blockPos = kBitMapBytes + startBlock * mBlockSize; + PRInt32 filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET); + if (filePos != blockPos) return NS_ERROR_UNEXPECTED; + + // read the blocks + PRInt32 bytesToRead = *bytesRead; + if ((bytesToRead <= 0) || ((PRUint32)bytesToRead > mBlockSize * numBlocks)) { + bytesToRead = mBlockSize * numBlocks; + } + *bytesRead = PR_Read(mFD, buffer, bytesToRead); + + return NS_OK; +} + + +/****************************************************************************** + * FlushBitMap + *****************************************************************************/ +nsresult +nsDiskCacheBlockFile::FlushBitMap() +{ + if (!mBitMapDirty) return NS_OK; + + // seek to bitmap + PRInt32 filePos = PR_Seek(mFD, 0, PR_SEEK_SET); + if (filePos != 0) return NS_ERROR_UNEXPECTED; + +#if defined(IS_LITTLE_ENDIAN) + PRUint32 bitmap[kBitMapWords]; + // Copy and swap to network format + PRUint32 *p = bitmap; + for (int i = 0; i < kBitMapWords; ++i, ++p) + *p = htonl(mBitMap[i]); +#else + PRUint32 *bitmap = mBitMap; +#endif + + // write bitmap + PRInt32 bytesWritten = PR_Write(mFD, bitmap, kBitMapBytes); + if (bytesWritten < kBitMapBytes) return NS_ERROR_UNEXPECTED; + + PRStatus err = PR_Sync(mFD); + if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED; + + mBitMapDirty = PR_F