1 /* -*- Mode: C++; indent-tab-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Util.h"
8 #include "mozilla/Attributes.h"
11 #include "nsDiskCache.h"
12 #include "nsDiskCacheDeviceSQL.h"
13 #include "nsCacheService.h"
14 #include "nsApplicationCache.h"
17 #include "nsNetUtil.h"
18 #include "nsAutoPtr.h"
20 #include "nsIPrefBranch.h"
21 #include "nsIPrefService.h"
23 #include "nsPrintfCString.h"
25 #include "nsArrayUtils.h"
27 #include "nsIVariant.h"
28 #include "nsThreadUtils.h"
29 #include "nsISerializable.h"
30 #include "nsSerializationHelper.h"
32 #include "mozIStorageService.h"
33 #include "mozIStorageStatement.h"
34 #include "mozIStorageFunction.h"
35 #include "mozStorageHelper.h"
37 #include "nsICacheVisitor.h"
38 #include "nsISeekableStream.h"
40 #include "mozilla/Telemetry.h"
43 #include "mozilla/storage.h"
45 using namespace mozilla
;
46 using namespace mozilla::storage
;
48 static const char OFFLINE_CACHE_DEVICE_ID
[] = { "offline" };
50 #define LOG(args) CACHE_LOG_DEBUG(args)
52 static uint32_t gNextTemporaryClientID
= 0;
54 /*****************************************************************************
59 EnsureDir(nsIFile
*dir
)
62 nsresult rv
= dir
->Exists(&exists
);
63 if (NS_SUCCEEDED(rv
) && !exists
)
64 rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
69 DecomposeCacheEntryKey(const nsCString
*fullKey
,
76 int32_t colon
= buf
.FindChar(':');
77 if (colon
== kNotFound
)
79 NS_ERROR("Invalid key");
82 buf
.SetCharAt('\0', colon
);
85 *key
= buf
.get() + colon
+ 1;
90 class AutoResetStatement
93 AutoResetStatement(mozIStorageStatement
*s
)
95 ~AutoResetStatement() { mStatement
->Reset(); }
96 mozIStorageStatement
*operator->() { return mStatement
; }
98 mozIStorageStatement
*mStatement
;
101 class EvictionObserver
104 EvictionObserver(mozIStorageConnection
*db
,
105 nsOfflineCacheEvictionFunction
*evictionFunction
)
106 : mDB(db
), mEvictionFunction(evictionFunction
)
108 mDB
->ExecuteSimpleSQL(
109 NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
110 " ON moz_cache FOR EACH ROW BEGIN SELECT"
111 " cache_eviction_observer("
112 " OLD.ClientID, OLD.key, OLD.generation);"
114 mEvictionFunction
->Reset();
119 mDB
->ExecuteSimpleSQL(
120 NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
121 mEvictionFunction
->Reset();
124 void Apply() { return mEvictionFunction
->Apply(); }
127 mozIStorageConnection
*mDB
;
128 nsRefPtr
<nsOfflineCacheEvictionFunction
> mEvictionFunction
;
131 #define DCACHE_HASH_MAX INT64_MAX
132 #define DCACHE_HASH_BITS 64
135 * nsOfflineCache::Hash(const char * key)
137 * This algorithm of this method implies nsOfflineCacheRecords will be stored
138 * in a certain order on disk. If the algorithm changes, existing cache
139 * map files may become invalid, and therefore the kCurrentVersion needs
143 DCacheHash(const char * key
)
145 // initval 0x7416f295 was chosen randomly
146 return (uint64_t(nsDiskCache::Hash(key
, 0)) << 32) | nsDiskCache::Hash(key
, 0x7416f295);
149 /******************************************************************************
150 * nsOfflineCacheEvictionFunction
153 NS_IMPL_THREADSAFE_ISUPPORTS1(nsOfflineCacheEvictionFunction
, mozIStorageFunction
)
155 // helper function for directly exposing the same data file binding
156 // path algorithm used in nsOfflineCacheBinding::Create
158 GetCacheDataFile(nsIFile
*cacheDir
, const char *key
,
159 int generation
, nsCOMPtr
<nsIFile
> &file
)
161 cacheDir
->Clone(getter_AddRefs(file
));
163 return NS_ERROR_OUT_OF_MEMORY
;
165 uint64_t hash
= DCacheHash(key
);
167 uint32_t dir1
= (uint32_t) (hash
& 0x0F);
168 uint32_t dir2
= (uint32_t)((hash
& 0xF0) >> 4);
172 file
->AppendNative(nsPrintfCString("%X", dir1
));
173 file
->AppendNative(nsPrintfCString("%X", dir2
));
176 PR_snprintf(leaf
, sizeof(leaf
), "%014llX-%X", hash
, generation
);
177 return file
->AppendNative(nsDependentCString(leaf
));
181 nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray
*values
, nsIVariant
**_retval
)
183 LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
188 nsresult rv
= values
->GetNumEntries(&numEntries
);
189 NS_ENSURE_SUCCESS(rv
, rv
);
190 NS_ASSERTION(numEntries
== 3, "unexpected number of arguments");
193 const char *clientID
= values
->AsSharedUTF8String(0, &valueLen
);
194 const char *key
= values
->AsSharedUTF8String(1, &valueLen
);
195 nsAutoCString
fullKey(clientID
);
196 fullKey
.AppendLiteral(":");
198 int generation
= values
->AsInt32(2);
200 // If the key is currently locked, refuse to delete this row.
201 if (mDevice
->IsLocked(fullKey
)) {
202 NS_ADDREF(*_retval
= new IntegerVariant(SQLITE_IGNORE
));
206 nsCOMPtr
<nsIFile
> file
;
207 rv
= GetCacheDataFile(mDevice
->CacheDirectory(), key
,
211 LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
212 key
, generation
, rv
));
216 mItems
.AppendObject(file
);
222 nsOfflineCacheEvictionFunction::Apply()
224 LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
226 for (int32_t i
= 0; i
< mItems
.Count(); i
++) {
227 #if defined(PR_LOGGING)
229 mItems
[i
]->GetNativePath(path
);
230 LOG((" removing %s\n", path
.get()));
233 mItems
[i
]->Remove(false);
239 class nsOfflineCacheDiscardCache
: public nsRunnable
242 nsOfflineCacheDiscardCache(nsOfflineCacheDevice
*device
,
247 , mClientID(clientID
)
253 if (mDevice
->IsActiveCache(mGroup
, mClientID
))
255 mDevice
->DeactivateGroup(mGroup
);
258 return mDevice
->EvictEntries(mClientID
.get());
262 nsRefPtr
<nsOfflineCacheDevice
> mDevice
;
267 /******************************************************************************
268 * nsOfflineCacheDeviceInfo
271 class nsOfflineCacheDeviceInfo MOZ_FINAL
: public nsICacheDeviceInfo
275 NS_DECL_NSICACHEDEVICEINFO
277 nsOfflineCacheDeviceInfo(nsOfflineCacheDevice
* device
)
282 nsOfflineCacheDevice
* mDevice
;
285 NS_IMPL_ISUPPORTS1(nsOfflineCacheDeviceInfo
, nsICacheDeviceInfo
)
288 nsOfflineCacheDeviceInfo::GetDescription(char **aDescription
)
290 *aDescription
= NS_strdup("Offline cache device");
291 return *aDescription
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
295 nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport
)
297 nsAutoCString buffer
;
298 buffer
.AssignLiteral(" <tr>\n"
299 " <th>Cache Directory:</th>\n"
301 nsIFile
*cacheDir
= mDevice
->CacheDirectory();
306 nsresult rv
= cacheDir
->GetPath(path
);
307 if (NS_SUCCEEDED(rv
))
308 AppendUTF16toUTF8(path
, buffer
);
310 buffer
.AppendLiteral("directory unavailable");
312 buffer
.AppendLiteral("</td>\n"
315 *usageReport
= ToNewCString(buffer
);
317 return NS_ERROR_OUT_OF_MEMORY
;
323 nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount
)
325 *aEntryCount
= mDevice
->EntryCount();
330 nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize
)
332 *aTotalSize
= mDevice
->CacheSize();
337 nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize
)
339 *aMaximumSize
= mDevice
->CacheCapacity();
343 /******************************************************************************
344 * nsOfflineCacheBinding
347 class nsOfflineCacheBinding MOZ_FINAL
: public nsISupports
352 static nsOfflineCacheBinding
*
353 Create(nsIFile
*cacheDir
, const nsCString
*key
, int generation
);
355 enum { FLAG_NEW_ENTRY
= 1 };
357 nsCOMPtr
<nsIFile
> mDataFile
;
361 bool IsNewEntry() { return mFlags
& FLAG_NEW_ENTRY
; }
362 void MarkNewEntry() { mFlags
|= FLAG_NEW_ENTRY
; }
363 void ClearNewEntry() { mFlags
&= ~FLAG_NEW_ENTRY
; }
366 NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheBinding
)
368 nsOfflineCacheBinding
*
369 nsOfflineCacheBinding::Create(nsIFile
*cacheDir
,
370 const nsCString
*fullKey
,
373 nsCOMPtr
<nsIFile
> file
;
374 cacheDir
->Clone(getter_AddRefs(file
));
378 nsAutoCString keyBuf
;
379 const char *cid
, *key
;
380 if (!DecomposeCacheEntryKey(fullKey
, &cid
, &key
, keyBuf
))
383 uint64_t hash
= DCacheHash(key
);
385 uint32_t dir1
= (uint32_t) (hash
& 0x0F);
386 uint32_t dir2
= (uint32_t)((hash
& 0xF0) >> 4);
390 // XXX we might want to create these directories up-front
392 file
->AppendNative(nsPrintfCString("%X", dir1
));
393 file
->Create(nsIFile::DIRECTORY_TYPE
, 00700);
395 file
->AppendNative(nsPrintfCString("%X", dir2
));
396 file
->Create(nsIFile::DIRECTORY_TYPE
, 00700);
401 if (generation
== -1)
403 file
->AppendNative(NS_LITERAL_CSTRING("placeholder"));
405 for (generation
= 0; ; ++generation
)
407 PR_snprintf(leaf
, sizeof(leaf
), "%014llX-%X", hash
, generation
);
409 rv
= file
->SetNativeLeafName(nsDependentCString(leaf
));
412 rv
= file
->Create(nsIFile::NORMAL_FILE_TYPE
, 00600);
413 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_ALREADY_EXISTS
)
415 if (NS_SUCCEEDED(rv
))
421 PR_snprintf(leaf
, sizeof(leaf
), "%014llX-%X", hash
, generation
);
422 rv
= file
->AppendNative(nsDependentCString(leaf
));
427 nsOfflineCacheBinding
*binding
= new nsOfflineCacheBinding
;
431 binding
->mDataFile
.swap(file
);
432 binding
->mGeneration
= generation
;
437 /******************************************************************************
438 * nsOfflineCacheRecord
441 struct nsOfflineCacheRecord
443 const char *clientID
;
445 const uint8_t *metaData
;
446 uint32_t metaDataLen
;
451 int64_t lastModified
;
452 int64_t expirationTime
;
455 static nsCacheEntry
*
456 CreateCacheEntry(nsOfflineCacheDevice
*device
,
457 const nsCString
*fullKey
,
458 const nsOfflineCacheRecord
&rec
)
462 if (device
->IsLocked(*fullKey
)) {
466 nsresult rv
= nsCacheEntry::Create(fullKey
->get(), // XXX enable sharing
467 nsICache::STREAM_BASED
,
468 nsICache::STORE_OFFLINE
,
473 entry
->SetFetchCount((uint32_t) rec
.fetchCount
);
474 entry
->SetLastFetched(SecondsFromPRTime(rec
.lastFetched
));
475 entry
->SetLastModified(SecondsFromPRTime(rec
.lastModified
));
476 entry
->SetExpirationTime(SecondsFromPRTime(rec
.expirationTime
));
477 entry
->SetDataSize((uint32_t) rec
.dataSize
);
479 entry
->UnflattenMetaData((const char *) rec
.metaData
, rec
.metaDataLen
);
481 // Restore security info, if present
482 const char* info
= entry
->GetMetaDataElement("security-info");
484 nsCOMPtr
<nsISupports
> infoObj
;
485 rv
= NS_DeserializeObject(nsDependentCString(info
),
486 getter_AddRefs(infoObj
));
491 entry
->SetSecurityInfo(infoObj
);
494 // create a binding object for this entry
495 nsOfflineCacheBinding
*binding
=
496 nsOfflineCacheBinding::Create(device
->CacheDirectory(),
504 entry
->SetData(binding
);
510 /******************************************************************************
511 * nsOfflineCacheEntryInfo
514 class nsOfflineCacheEntryInfo MOZ_FINAL
: public nsICacheEntryInfo
518 NS_DECL_NSICACHEENTRYINFO
520 nsOfflineCacheRecord
*mRec
;
523 NS_IMPL_ISUPPORTS1(nsOfflineCacheEntryInfo
, nsICacheEntryInfo
)
526 nsOfflineCacheEntryInfo::GetClientID(char **result
)
528 *result
= NS_strdup(mRec
->clientID
);
529 return *result
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
533 nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID
)
535 *deviceID
= NS_strdup(OFFLINE_CACHE_DEVICE_ID
);
536 return *deviceID
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
540 nsOfflineCacheEntryInfo::GetKey(nsACString
&clientKey
)
542 clientKey
.Assign(mRec
->key
);
547 nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount
)
549 *aFetchCount
= mRec
->fetchCount
;
554 nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched
)
556 *aLastFetched
= SecondsFromPRTime(mRec
->lastFetched
);
561 nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified
)
563 *aLastModified
= SecondsFromPRTime(mRec
->lastModified
);
568 nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime
)
570 *aExpirationTime
= SecondsFromPRTime(mRec
->expirationTime
);
575 nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased
)
577 *aStreamBased
= true;
582 nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize
)
584 *aDataSize
= mRec
->dataSize
;
589 /******************************************************************************
590 * nsApplicationCacheNamespace
593 NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace
, nsIApplicationCacheNamespace
)
596 nsApplicationCacheNamespace::Init(uint32_t itemType
,
597 const nsACString
&namespaceSpec
,
598 const nsACString
&data
)
600 mItemType
= itemType
;
601 mNamespaceSpec
= namespaceSpec
;
607 nsApplicationCacheNamespace::GetItemType(uint32_t *out
)
614 nsApplicationCacheNamespace::GetNamespaceSpec(nsACString
&out
)
616 out
= mNamespaceSpec
;
621 nsApplicationCacheNamespace::GetData(nsACString
&out
)
627 /******************************************************************************
631 NS_IMPL_ISUPPORTS2(nsApplicationCache
,
633 nsISupportsWeakReference
)
635 nsApplicationCache::nsApplicationCache()
641 nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice
*device
,
642 const nsACString
&group
,
643 const nsACString
&clientID
)
646 , mClientID(clientID
)
651 nsApplicationCache::~nsApplicationCache()
656 mDevice
->mCaches
.Remove(mClientID
);
658 // If this isn't an active cache anymore, it can be destroyed.
659 if (mValid
&& !mDevice
->IsActiveCache(mGroup
, mClientID
))
664 nsApplicationCache::MarkInvalid()
670 nsApplicationCache::InitAsHandle(const nsACString
&groupId
,
671 const nsACString
&clientId
)
673 NS_ENSURE_FALSE(mDevice
, NS_ERROR_ALREADY_INITIALIZED
);
674 NS_ENSURE_TRUE(mGroup
.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED
);
677 mClientID
= clientId
;
682 nsApplicationCache::GetManifestURI(nsIURI
**out
)
684 nsCOMPtr
<nsIURI
> uri
;
685 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), mGroup
);
686 NS_ENSURE_SUCCESS(rv
, rv
);
688 rv
= uri
->CloneIgnoringRef(out
);
689 NS_ENSURE_SUCCESS(rv
, rv
);
695 nsApplicationCache::GetGroupID(nsACString
&out
)
702 nsApplicationCache::GetClientID(nsACString
&out
)
709 nsApplicationCache::GetProfileDirectory(nsIFile
**out
)
711 if (mDevice
->BaseDirectory())
712 NS_ADDREF(*out
= mDevice
->BaseDirectory());
720 nsApplicationCache::GetActive(bool *out
)
722 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
724 *out
= mDevice
->IsActiveCache(mGroup
, mClientID
);
729 nsApplicationCache::Activate()
731 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
732 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
734 mDevice
->ActivateCache(mGroup
, mClientID
);
736 if (mDevice
->AutoShutdown(this))
743 nsApplicationCache::Discard()
745 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
746 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
750 nsRefPtr
<nsIRunnable
> ev
=
751 new nsOfflineCacheDiscardCache(mDevice
, mGroup
, mClientID
);
752 nsresult rv
= nsCacheService::DispatchToCacheIOThread(ev
);
757 nsApplicationCache::MarkEntry(const nsACString
&key
,
760 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
761 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
763 return mDevice
->MarkEntry(mClientID
, key
, typeBits
);
768 nsApplicationCache::UnmarkEntry(const nsACString
&key
,
771 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
772 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
774 return mDevice
->UnmarkEntry(mClientID
, key
, typeBits
);
778 nsApplicationCache::GetTypes(const nsACString
&key
,
781 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
782 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
784 return mDevice
->GetTypes(mClientID
, key
, typeBits
);
788 nsApplicationCache::GatherEntries(uint32_t typeBits
,
792 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
793 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
795 return mDevice
->GatherEntries(mClientID
, typeBits
, count
, keys
);
799 nsApplicationCache::AddNamespaces(nsIArray
*namespaces
)
801 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
802 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
807 mozStorageTransaction
transaction(mDevice
->mDB
, false);
810 nsresult rv
= namespaces
->GetLength(&length
);
811 NS_ENSURE_SUCCESS(rv
, rv
);
813 for (uint32_t i
= 0; i
< length
; i
++) {
814 nsCOMPtr
<nsIApplicationCacheNamespace
> ns
=
815 do_QueryElementAt(namespaces
, i
);
817 rv
= mDevice
->AddNamespace(mClientID
, ns
);
818 NS_ENSURE_SUCCESS(rv
, rv
);
822 rv
= transaction
.Commit();
823 NS_ENSURE_SUCCESS(rv
, rv
);
829 nsApplicationCache::GetMatchingNamespace(const nsACString
&key
,
830 nsIApplicationCacheNamespace
**out
)
833 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
834 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
836 return mDevice
->GetMatchingNamespace(mClientID
, key
, out
);
840 nsApplicationCache::GetUsage(uint32_t *usage
)
842 NS_ENSURE_TRUE(mValid
, NS_ERROR_NOT_AVAILABLE
);
843 NS_ENSURE_TRUE(mDevice
, NS_ERROR_NOT_AVAILABLE
);
845 return mDevice
->GetUsage(mClientID
, usage
);
848 /******************************************************************************
850 *****************************************************************************/
852 class nsCloseDBEvent
: public nsRunnable
{
854 nsCloseDBEvent(mozIStorageConnection
*aDB
)
866 virtual ~nsCloseDBEvent() {}
869 nsCOMPtr
<mozIStorageConnection
> mDB
;
874 /******************************************************************************
875 * nsOfflineCacheDevice
878 NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheDevice
)
880 nsOfflineCacheDevice::nsOfflineCacheDevice()
884 , mAutoShutdown(false)
890 nsOfflineCacheDevice::GetStrictFileOriginPolicy()
892 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
895 if (prefs
&& NS_SUCCEEDED(prefs
->GetBoolPref("security.fileuri.strict_origin_policy", &retval
)))
898 // As default value use true (be more strict)
903 nsOfflineCacheDevice::CacheSize()
905 AutoResetStatement
statement(mStatement_CacheSize
);
908 nsresult rv
= statement
->ExecuteStep(&hasRows
);
909 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && hasRows
, 0);
911 return (uint32_t) statement
->AsInt32(0);
915 nsOfflineCacheDevice::EntryCount()
917 AutoResetStatement
statement(mStatement_EntryCount
);
920 nsresult rv
= statement
->ExecuteStep(&hasRows
);
921 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && hasRows
, 0);
923 return (uint32_t) statement
->AsInt32(0);
927 nsOfflineCacheDevice::UpdateEntry(nsCacheEntry
*entry
)
929 // Decompose the key into "ClientID" and "Key"
930 nsAutoCString keyBuf
;
931 const char *cid
, *key
;
933 if (!DecomposeCacheEntryKey(entry
->Key(), &cid
, &key
, keyBuf
))
934 return NS_ERROR_UNEXPECTED
;
936 // Store security info, if it is serializable
937 nsCOMPtr
<nsISupports
> infoObj
= entry
->SecurityInfo();
938 nsCOMPtr
<nsISerializable
> serializable
= do_QueryInterface(infoObj
);
939 if (infoObj
&& !serializable
)
940 return NS_ERROR_UNEXPECTED
;
944 nsresult rv
= NS_SerializeToString(serializable
, info
);
945 NS_ENSURE_SUCCESS(rv
, rv
);
947 rv
= entry
->SetMetaDataElement("security-info", info
.get());
948 NS_ENSURE_SUCCESS(rv
, rv
);
951 nsCString metaDataBuf
;
952 uint32_t mdSize
= entry
->MetaDataSize();
953 if (!EnsureStringLength(metaDataBuf
, mdSize
))
954 return NS_ERROR_OUT_OF_MEMORY
;
955 char *md
= metaDataBuf
.BeginWriting();
956 entry
->FlattenMetaData(md
, mdSize
);
958 nsOfflineCacheRecord rec
;
959 rec
.metaData
= (const uint8_t *) md
;
960 rec
.metaDataLen
= mdSize
;
961 rec
.dataSize
= entry
->DataSize();
962 rec
.fetchCount
= entry
->FetchCount();
963 rec
.lastFetched
= PRTimeFromSeconds(entry
->LastFetched());
964 rec
.lastModified
= PRTimeFromSeconds(entry
->LastModified());
965 rec
.expirationTime
= PRTimeFromSeconds(entry
->ExpirationTime());
967 AutoResetStatement
statement(mStatement_UpdateEntry
);
970 rv
= statement
->BindBlobByIndex(0, rec
.metaData
, rec
.metaDataLen
);
971 nsresult tmp
= statement
->BindInt32ByIndex(1, rec
.dataSize
);
972 if (NS_FAILED(tmp
)) {
975 tmp
= statement
->BindInt32ByIndex(2, rec
.fetchCount
);
976 if (NS_FAILED(tmp
)) {
979 tmp
= statement
->BindInt64ByIndex(3, rec
.lastFetched
);
980 if (NS_FAILED(tmp
)) {
983 tmp
= statement
->BindInt64ByIndex(4, rec
.lastModified
);
984 if (NS_FAILED(tmp
)) {
987 tmp
= statement
->BindInt64ByIndex(5, rec
.expirationTime
);
988 if (NS_FAILED(tmp
)) {
991 tmp
= statement
->BindUTF8StringByIndex(6, nsDependentCString(cid
));
992 if (NS_FAILED(tmp
)) {
995 tmp
= statement
->BindUTF8StringByIndex(7, nsDependentCString(key
));
996 if (NS_FAILED(tmp
)) {
999 NS_ENSURE_SUCCESS(rv
, rv
);
1002 rv
= statement
->ExecuteStep(&hasRows
);
1003 NS_ENSURE_SUCCESS(rv
, rv
);
1005 NS_ASSERTION(!hasRows
, "UPDATE should not result in output");
1010 nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry
*entry
, uint32_t newSize
)
1012 // Decompose the key into "ClientID" and "Key"
1013 nsAutoCString keyBuf
;
1014 const char *cid
, *key
;
1015 if (!DecomposeCacheEntryKey(entry
->Key(), &cid
, &key
, keyBuf
))
1016 return NS_ERROR_UNEXPECTED
;
1018 AutoResetStatement
statement(mStatement_UpdateEntrySize
);
1020 nsresult rv
= statement
->BindInt32ByIndex(0, newSize
);
1021 nsresult tmp
= statement
->BindUTF8StringByIndex(1, nsDependentCString(cid
));
1022 if (NS_FAILED(tmp
)) {
1025 tmp
= statement
->BindUTF8StringByIndex(2, nsDependentCString(key
));
1026 if (NS_FAILED(tmp
)) {
1029 NS_ENSURE_SUCCESS(rv
, rv
);
1032 rv
= statement
->ExecuteStep(&hasRows
);
1033 NS_ENSURE_SUCCESS(rv
, rv
);
1035 NS_ASSERTION(!hasRows
, "UPDATE should not result in output");
1040 nsOfflineCacheDevice::DeleteEntry(nsCacheEntry
*entry
, bool deleteData
)
1044 nsresult rv
= DeleteData(entry
);
1049 // Decompose the key into "ClientID" and "Key"
1050 nsAutoCString keyBuf
;
1051 const char *cid
, *key
;
1052 if (!DecomposeCacheEntryKey(entry
->Key(), &cid
, &key
, keyBuf
))
1053 return NS_ERROR_UNEXPECTED
;
1055 AutoResetStatement
statement(mStatement_DeleteEntry
);
1057 nsresult rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(cid
));
1058 nsresult rv2
= statement
->BindUTF8StringByIndex(1, nsDependentCString(key
));
1059 NS_ENSURE_SUCCESS(rv
, rv
);
1060 NS_ENSURE_SUCCESS(rv2
, rv2
);
1063 rv
= statement
->ExecuteStep(&hasRows
);
1064 NS_ENSURE_SUCCESS(rv
, rv
);
1066 NS_ASSERTION(!hasRows
, "DELETE should not result in output");
1071 nsOfflineCacheDevice::DeleteData(nsCacheEntry
*entry
)
1073 nsOfflineCacheBinding
*binding
= (nsOfflineCacheBinding
*) entry
->Data();
1074 NS_ENSURE_STATE(binding
);
1076 return binding
->mDataFile
->Remove(false);
1080 * nsCacheDevice implementation
1083 // This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
1084 // allow a template (mozilla::ArrayLength) to be instantiated based on a local
1086 struct StatementSql
{
1087 nsCOMPtr
<mozIStorageStatement
> &statement
;
1089 StatementSql (nsCOMPtr
<mozIStorageStatement
> &aStatement
, const char *aSql
):
1090 statement (aStatement
), sql (aSql
) {}
1094 nsOfflineCacheDevice::Init()
1096 NS_ENSURE_TRUE(!mDB
, NS_ERROR_ALREADY_INITIALIZED
);
1098 // SetCacheParentDirectory must have been called
1099 NS_ENSURE_TRUE(mCacheDirectory
, NS_ERROR_UNEXPECTED
);
1101 // make sure the cache directory exists
1102 nsresult rv
= EnsureDir(mCacheDirectory
);
1103 NS_ENSURE_SUCCESS(rv
, rv
);
1105 // build path to index file
1106 nsCOMPtr
<nsIFile
> indexFile
;
1107 rv
= mCacheDirectory
->Clone(getter_AddRefs(indexFile
));
1108 NS_ENSURE_SUCCESS(rv
, rv
);
1109 rv
= indexFile
->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
1110 NS_ENSURE_SUCCESS(rv
, rv
);
1112 nsCOMPtr
<mozIStorageService
> ss
=
1113 do_GetService("@mozilla.org/storage/service;1", &rv
);
1114 NS_ENSURE_SUCCESS(rv
, rv
);
1116 rv
= ss
->OpenDatabase(indexFile
, getter_AddRefs(mDB
));
1117 NS_ENSURE_SUCCESS(rv
, rv
);
1119 mInitThread
= do_GetCurrentThread();
1121 mDB
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
1123 // XXX ... other initialization steps
1125 // XXX in the future we may wish to verify the schema for moz_cache
1126 // perhaps using "PRAGMA table_info" ?
1130 // "Generation" is the data file generation number.
1132 rv
= mDB
->ExecuteSimpleSQL(
1133 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1137 " Generation INTEGER,\n"
1138 " DataSize INTEGER,\n"
1139 " FetchCount INTEGER,\n"
1140 " LastFetched INTEGER,\n"
1141 " LastModified INTEGER,\n"
1142 " ExpirationTime INTEGER,\n"
1143 " ItemType INTEGER DEFAULT 0\n"
1145 NS_ENSURE_SUCCESS(rv
, rv
);
1147 // Databases from 1.9.0 don't have the ItemType column. Add the column
1148 // here, but don't worry about failures (the column probably already exists)
1149 mDB
->ExecuteSimpleSQL(
1150 NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1152 // Create the table for storing cache groups. All actions on
1153 // moz_cache_groups use the GroupID, so use it as the primary key.
1154 rv
= mDB
->ExecuteSimpleSQL(
1155 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1156 " GroupID TEXT PRIMARY KEY,\n"
1157 " ActiveClientID TEXT\n"
1159 NS_ENSURE_SUCCESS(rv
, rv
);
1161 mDB
->ExecuteSimpleSQL(
1162 NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1163 "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1165 // ClientID: clientID joining moz_cache and moz_cache_namespaces
1167 // Data: Data associated with this namespace (e.g. a fallback URI
1168 // for fallback entries).
1169 // ItemType: the type of namespace.
1170 rv
= mDB
->ExecuteSimpleSQL(
1171 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1172 " moz_cache_namespaces (\n"
1174 " NameSpace TEXT,\n"
1176 " ItemType INTEGER\n"
1178 NS_ENSURE_SUCCESS(rv
, rv
);
1180 // Databases from 1.9.0 have a moz_cache_index that should be dropped
1181 rv
= mDB
->ExecuteSimpleSQL(
1182 NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1183 NS_ENSURE_SUCCESS(rv
, rv
);
1185 // Key/ClientID pairs should be unique in the database. All queries
1186 // against moz_cache use the Key (which is also the most unique), so
1187 // use it as the primary key for this index.
1188 rv
= mDB
->ExecuteSimpleSQL(
1189 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1190 " moz_cache_key_clientid_index"
1191 " ON moz_cache (Key, ClientID);"));
1192 NS_ENSURE_SUCCESS(rv
, rv
);
1194 // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1195 rv
= mDB
->ExecuteSimpleSQL(
1196 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1197 " moz_cache_namespaces_clientid_index"
1198 " ON moz_cache_namespaces (ClientID, NameSpace);"));
1199 NS_ENSURE_SUCCESS(rv
, rv
);
1201 // Used for namespace lookups.
1202 rv
= mDB
->ExecuteSimpleSQL(
1203 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1204 " moz_cache_namespaces_namespace_index"
1205 " ON moz_cache_namespaces (NameSpace);"));
1206 NS_ENSURE_SUCCESS(rv
, rv
);
1209 mEvictionFunction
= new nsOfflineCacheEvictionFunction(this);
1210 if (!mEvictionFunction
) return NS_ERROR_OUT_OF_MEMORY
;
1212 rv
= mDB
->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction
);
1213 NS_ENSURE_SUCCESS(rv
, rv
);
1215 // create all (most) of our statements up front
1216 StatementSql prepared
[] = {
1217 StatementSql ( mStatement_CacheSize
, "SELECT Sum(DataSize) from moz_cache;" ),
1218 StatementSql ( mStatement_ApplicationCacheSize
, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
1219 StatementSql ( mStatement_EntryCount
, "SELECT count(*) from moz_cache;" ),
1220 StatementSql ( mStatement_UpdateEntry
, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1221 StatementSql ( mStatement_UpdateEntrySize
, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1222 StatementSql ( mStatement_DeleteEntry
, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1223 StatementSql ( mStatement_FindEntry
, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1224 StatementSql ( mStatement_BindEntry
, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
1226 StatementSql ( mStatement_MarkEntry
, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1227 StatementSql ( mStatement_UnmarkEntry
, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1228 StatementSql ( mStatement_GetTypes
, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1229 StatementSql ( mStatement_CleanupUnmarked
, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1230 StatementSql ( mStatement_GatherEntries
, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1232 StatementSql ( mStatement_ActivateClient
, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1233 StatementSql ( mStatement_DeactivateGroup
, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1234 StatementSql ( mStatement_FindClient
, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1236 // Search for namespaces that match the URI. Use the <= operator
1237 // to ensure that we use the index on moz_cache_namespaces.
1238 StatementSql ( mStatement_FindClientByNamespace
, "SELECT ns.ClientID, ns.ItemType FROM"
1239 " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1240 " ON ns.ClientID = groups.ActiveClientID"
1241 " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1242 " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1243 StatementSql ( mStatement_FindNamespaceEntry
, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
1244 " WHERE ClientID = ?1"
1245 " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1246 " ORDER BY NameSpace DESC;"),
1247 StatementSql ( mStatement_InsertNamespaceEntry
, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1248 StatementSql ( mStatement_EnumerateApps
, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
1249 StatementSql ( mStatement_EnumerateGroups
, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
1250 StatementSql ( mStatement_EnumerateGroupsTimeOrder
, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
1252 for (uint32_t i
= 0; NS_SUCCEEDED(rv
) && i
< ArrayLength(prepared
); ++i
)
1254 LOG(("Creating statement: %s\n", prepared
[i
].sql
));
1256 rv
= mDB
->CreateStatement(nsDependentCString(prepared
[i
].sql
),
1257 getter_AddRefs(prepared
[i
].statement
));
1258 NS_ENSURE_SUCCESS(rv
, rv
);
1261 rv
= InitActiveCaches();
1262 NS_ENSURE_SUCCESS(rv
, rv
);
1270 GetGroupForCache(const nsCSubstring
&clientID
, nsCString
&group
)
1272 group
.Assign(clientID
);
1273 group
.Truncate(group
.FindChar('|'));
1274 NS_UnescapeURL(group
);
1280 AppendJARIdentifier(nsACString
&_result
, int32_t appId
, bool isInBrowserElement
)
1282 _result
.Append('#');
1283 _result
.AppendInt(appId
);
1284 _result
.Append('+');
1285 _result
.Append(isInBrowserElement
? 't' : 'f');
1291 GetJARIdentifier(nsIURI
*aURI
,
1292 uint32_t appId
, bool isInBrowserElement
,
1293 nsACString
&_result
)
1297 // These lines are here for compatibility only. We must not fill the
1298 // JAR identifier when this is no-app context, otherwise web content
1299 // offline application cache loads would not be satisfied (cache would
1301 if (!isInBrowserElement
&& appId
== NECKO_NO_APP_ID
)
1304 // This load context has some special attributes, create a jar identifier
1305 return AppendJARIdentifier(_result
, appId
, isInBrowserElement
);
1312 nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI
*aManifestURL
,
1313 uint32_t appId
, bool isInBrowserElement
,
1314 nsACString
&_result
)
1316 nsCOMPtr
<nsIURI
> newURI
;
1317 nsresult rv
= aManifestURL
->CloneIgnoringRef(getter_AddRefs(newURI
));
1318 NS_ENSURE_SUCCESS(rv
, rv
);
1320 nsAutoCString manifestSpec
;
1321 rv
= newURI
->GetAsciiSpec(manifestSpec
);
1322 NS_ENSURE_SUCCESS(rv
, rv
);
1324 _result
.Assign(manifestSpec
);
1326 nsAutoCString jarid
;
1327 rv
= GetJARIdentifier(aManifestURL
, appId
, isInBrowserElement
, jarid
);
1328 NS_ENSURE_SUCCESS(rv
, rv
);
1330 // Include JAR ID, i.e. the extended origin if present.
1331 if (!jarid
.IsEmpty())
1332 _result
.Append(jarid
);
1338 nsOfflineCacheDevice::InitActiveCaches()
1341 mActiveCachesByGroup
.Init();
1343 mActiveCaches
.Init(5);
1345 mLockedEntries
.Init(64);
1347 AutoResetStatement
statement(mStatement_EnumerateGroups
);
1350 nsresult rv
= statement
->ExecuteStep(&hasRows
);
1351 NS_ENSURE_SUCCESS(rv
, rv
);
1355 nsAutoCString group
;
1356 statement
->GetUTF8String(0, group
);
1358 statement
->GetUTF8String(1, clientID
);
1360 mActiveCaches
.PutEntry(clientID
);
1361 mActiveCachesByGroup
.Put(group
, new nsCString(clientID
));
1363 rv
= statement
->ExecuteStep(&hasRows
);
1364 NS_ENSURE_SUCCESS(rv
, rv
);
1372 nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString
&key
,
1373 nsIWeakReference
*weakRef
,
1376 nsCOMPtr
<nsIApplicationCache
> obj
= do_QueryReferent(weakRef
);
1379 nsApplicationCache
*appCache
= static_cast<nsApplicationCache
*>(obj
.get());
1380 appCache
->MarkInvalid();
1383 return PL_DHASH_NEXT
;
1387 nsOfflineCacheDevice::Shutdown()
1389 NS_ENSURE_TRUE(mDB
, NS_ERROR_NOT_INITIALIZED
);
1391 if (mCaches
.IsInitialized())
1392 mCaches
.EnumerateRead(ShutdownApplicationCache
, this);
1395 EvictionObserver
evictionObserver(mDB
, mEvictionFunction
);
1397 // Delete all rows whose clientID is not an active clientID.
1398 nsresult rv
= mDB
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1399 "DELETE FROM moz_cache WHERE rowid IN"
1400 " (SELECT moz_cache.rowid FROM"
1401 " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1402 " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1403 " WHERE moz_cache_groups.GroupID ISNULL)"));
1406 NS_WARNING("Failed to clean up unused application caches.");
1408 evictionObserver
.Apply();
1410 // Delete all namespaces whose clientID is not an active clientID.
1411 rv
= mDB
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1412 "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1413 " (SELECT moz_cache_namespaces.rowid FROM"
1414 " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1415 " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
1416 " WHERE moz_cache_groups.GroupID ISNULL)"));
1419 NS_WARNING("Failed to clean up namespaces.");
1421 mEvictionFunction
= 0;
1423 mStatement_CacheSize
= nullptr;
1424 mStatement_ApplicationCacheSize
= nullptr;
1425 mStatement_EntryCount
= nullptr;
1426 mStatement_UpdateEntry
= nullptr;
1427 mStatement_UpdateEntrySize
= nullptr;
1428 mStatement_DeleteEntry
= nullptr;
1429 mStatement_FindEntry
= nullptr;
1430 mStatement_BindEntry
= nullptr;
1431 mStatement_ClearDomain
= nullptr;
1432 mStatement_MarkEntry
= nullptr;
1433 mStatement_UnmarkEntry
= nullptr;
1434 mStatement_GetTypes
= nullptr;
1435 mStatement_FindNamespaceEntry
= nullptr;
1436 mStatement_InsertNamespaceEntry
= nullptr;
1437 mStatement_CleanupUnmarked
= nullptr;
1438 mStatement_GatherEntries
= nullptr;
1439 mStatement_ActivateClient
= nullptr;
1440 mStatement_DeactivateGroup
= nullptr;
1441 mStatement_FindClient
= nullptr;
1442 mStatement_FindClientByNamespace
= nullptr;
1443 mStatement_EnumerateApps
= nullptr;
1444 mStatement_EnumerateGroups
= nullptr;
1445 mStatement_EnumerateGroupsTimeOrder
= nullptr;
1448 // Close Database on the correct thread
1449 bool isOnCurrentThread
= true;
1451 mInitThread
->IsOnCurrentThread(&isOnCurrentThread
);
1453 if (!isOnCurrentThread
) {
1454 nsCOMPtr
<nsIRunnable
> ev
= new nsCloseDBEvent(mDB
);
1457 mInitThread
->Dispatch(ev
, NS_DISPATCH_NORMAL
);
1465 mInitThread
= nullptr;
1471 nsOfflineCacheDevice::GetDeviceID()
1473 return OFFLINE_CACHE_DEVICE_ID
;
1477 nsOfflineCacheDevice::FindEntry(nsCString
*fullKey
, bool *collision
)
1479 mozilla::Telemetry::AutoTimer
<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2
> timer
;
1480 LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey
->get()));
1482 // SELECT * FROM moz_cache WHERE key = ?
1484 // Decompose the key into "ClientID" and "Key"
1485 nsAutoCString keyBuf
;
1486 const char *cid
, *key
;
1487 if (!DecomposeCacheEntryKey(fullKey
, &cid
, &key
, keyBuf
))
1490 AutoResetStatement
statement(mStatement_FindEntry
);
1492 nsresult rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(cid
));
1493 nsresult rv2
= statement
->BindUTF8StringByIndex(1, nsDependentCString(key
));
1494 NS_ENSURE_SUCCESS(rv
, nullptr);
1495 NS_ENSURE_SUCCESS(rv2
, nullptr);
1498 rv
= statement
->ExecuteStep(&hasRows
);
1499 if (NS_FAILED(rv
) || !hasRows
)
1500 return nullptr; // entry not found
1502 nsOfflineCacheRecord rec
;
1503 statement
->GetSharedBlob(0, &rec
.metaDataLen
,
1504 (const uint8_t **) &rec
.metaData
);
1505 rec
.generation
= statement
->AsInt32(1);
1506 rec
.dataSize
= statement
->AsInt32(2);
1507 rec
.fetchCount
= statement
->AsInt32(3);
1508 rec
.lastFetched
= statement
->AsInt64(4);
1509 rec
.lastModified
= statement
->AsInt64(5);
1510 rec
.expirationTime
= statement
->AsInt64(6);
1512 LOG(("entry: [%u %d %d %d %lld %lld %lld]\n",
1519 rec
.expirationTime
));
1521 nsCacheEntry
*entry
= CreateCacheEntry(this, fullKey
, rec
);
1525 // make sure that the data file exists
1526 nsOfflineCacheBinding
*binding
= (nsOfflineCacheBinding
*)entry
->Data();
1528 rv
= binding
->mDataFile
->IsFile(&isFile
);
1529 if (NS_FAILED(rv
) || !isFile
)
1531 DeleteEntry(entry
, false);
1544 nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry
*entry
)
1546 LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1547 entry
->Key()->get()));
1549 // This method is called to inform us that the nsCacheEntry object is going
1550 // away. We should persist anything that needs to be persisted, or if the
1551 // entry is doomed, we can go ahead and clear its storage.
1553 if (entry
->IsDoomed())
1555 // remove corresponding row and file if they exist
1557 // the row should have been removed in DoomEntry... we could assert that
1558 // that happened. otherwise, all we have to do here is delete the file
1562 else if (((nsOfflineCacheBinding
*)entry
->Data())->IsNewEntry())
1564 // UPDATE the database row
1566 // Only new entries are updated, since offline cache is updated in
1567 // transactions. New entries are those who is returned from
1570 LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
1573 LOG(("nsOfflineCacheDevice::DeactivateEntry "
1574 "skipping update since entry is not dirty\n"));
1578 Unlock(*entry
->Key());
1586 nsOfflineCacheDevice::BindEntry(nsCacheEntry
*entry
)
1588 LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry
->Key()->get()));
1590 NS_ENSURE_STATE(!entry
->Data());
1592 // This method is called to inform us that we have a new entry. The entry
1593 // may collide with an existing entry in our DB, but if that happens we can
1594 // assume that the entry is not being used.
1596 // INSERT the database row
1598 // XXX Assumption: if the row already exists, then FindEntry would have
1599 // returned it. if that entry was doomed, then DoomEntry would have removed
1600 // it from the table. so, we should always have to insert at this point.
1602 // Decompose the key into "ClientID" and "Key"
1603 nsAutoCString keyBuf
;
1604 const char *cid
, *key
;
1605 if (!DecomposeCacheEntryKey(entry
->Key(), &cid
, &key
, keyBuf
))
1606 return NS_ERROR_UNEXPECTED
;
1608 // create binding, pick best generation number
1609 nsRefPtr
<nsOfflineCacheBinding
> binding
=
1610 nsOfflineCacheBinding::Create(mCacheDirectory
, entry
->Key(), -1);
1612 return NS_ERROR_OUT_OF_MEMORY
;
1613 binding
->MarkNewEntry();
1615 nsOfflineCacheRecord rec
;
1618 rec
.metaData
= NULL
; // don't write any metadata now.
1619 rec
.metaDataLen
= 0;
1620 rec
.generation
= binding
->mGeneration
;
1622 rec
.fetchCount
= entry
->FetchCount();
1623 rec
.lastFetched
= PRTimeFromSeconds(entry
->LastFetched());
1624 rec
.lastModified
= PRTimeFromSeconds(entry
->LastModified());
1625 rec
.expirationTime
= PRTimeFromSeconds(entry
->ExpirationTime());
1627 AutoResetStatement
statement(mStatement_BindEntry
);
1629 nsresult rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(rec
.clientID
));
1630 nsresult tmp
= statement
->BindUTF8StringByIndex(1, nsDependentCString(rec
.key
));
1631 if (NS_FAILED(tmp
)) {
1634 tmp
= statement
->BindBlobByIndex(2, rec
.metaData
, rec
.metaDataLen
);
1635 if (NS_FAILED(tmp
)) {
1638 tmp
= statement
->BindInt32ByIndex(3, rec
.generation
);
1639 if (NS_FAILED(tmp
)) {
1642 tmp
= statement
->BindInt32ByIndex(4, rec
.dataSize
);
1643 if (NS_FAILED(tmp
)) {
1646 tmp
= statement
->BindInt32ByIndex(5, rec
.fetchCount
);
1647 if (NS_FAILED(tmp
)) {
1650 tmp
= statement
->BindInt64ByIndex(6, rec
.lastFetched
);
1651 if (NS_FAILED(tmp
)) {
1654 tmp
= statement
->BindInt64ByIndex(7, rec
.lastModified
);
1655 if (NS_FAILED(tmp
)) {
1658 tmp
= statement
->BindInt64ByIndex(8, rec
.expirationTime
);
1659 if (NS_FAILED(tmp
)) {
1662 NS_ENSURE_SUCCESS(rv
, rv
);
1665 rv
= statement
->ExecuteStep(&hasRows
);
1666 NS_ENSURE_SUCCESS(rv
, rv
);
1667 NS_ASSERTION(!hasRows
, "INSERT should not result in output");
1669 entry
->SetData(binding
);
1672 Lock(*entry
->Key());
1678 nsOfflineCacheDevice::DoomEntry(nsCacheEntry
*entry
)
1680 LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry
->Key()->get()));
1682 // This method is called to inform us that we should mark the entry to be
1683 // deleted when it is no longer in use.
1685 // We can go ahead and delete the corresponding row in our table,
1686 // but we must not delete the file on disk until we are deactivated.
1687 // In another word, the file should be deleted if the entry had been
1690 DeleteEntry(entry
, !entry
->IsActive());
1694 nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry
*entry
,
1695 nsCacheAccessMode mode
,
1697 nsIInputStream
**result
)
1699 LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1700 entry
->Key()->get()));
1704 NS_ENSURE_TRUE(!offset
|| (offset
< entry
->DataSize()), NS_ERROR_INVALID_ARG
);
1706 // return an input stream to the entry's data file. the stream
1707 // may be read on a background thread.
1709 nsOfflineCacheBinding
*binding
= (nsOfflineCacheBinding
*) entry
->Data();
1710 NS_ENSURE_STATE(binding
);
1712 nsCOMPtr
<nsIInputStream
> in
;
1713 NS_NewLocalFileInputStream(getter_AddRefs(in
), binding
->mDataFile
, PR_RDONLY
);
1715 return NS_ERROR_UNEXPECTED
;
1717 // respect |offset| param
1720 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(in
);
1721 NS_ENSURE_TRUE(seekable
, NS_ERROR_UNEXPECTED
);
1723 seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1731 nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry
*entry
,
1732 nsCacheAccessMode mode
,
1734 nsIOutputStream
**result
)
1736 LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1737 entry
->Key()->get()));
1741 NS_ENSURE_TRUE(offset
<= entry
->DataSize(), NS_ERROR_INVALID_ARG
);
1743 // return an output stream to the entry's data file. we can assume
1744 // that the output stream will only be used on the main thread.
1746 nsOfflineCacheBinding
*binding
= (nsOfflineCacheBinding
*) entry
->Data();
1747 NS_ENSURE_STATE(binding
);
1749 nsCOMPtr
<nsIOutputStream
> out
;
1750 NS_NewLocalFileOutputStream(getter_AddRefs(out
), binding
->mDataFile
,
1751 PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
,
1754 return NS_ERROR_UNEXPECTED
;
1756 // respect |offset| param
1757 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(out
);
1758 NS_ENSURE_TRUE(seekable
, NS_ERROR_UNEXPECTED
);
1760 seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, offset
);
1762 // truncate the file at the given offset
1765 nsCOMPtr
<nsIOutputStream
> bufferedOut
;
1767 NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut
), out
, 16 * 1024);
1768 NS_ENSURE_SUCCESS(rv
, rv
);
1770 bufferedOut
.swap(*result
);
1775 nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry
*entry
, nsIFile
**result
)
1777 LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1778 entry
->Key()->get()));
1780 nsOfflineCacheBinding
*binding
= (nsOfflineCacheBinding
*) entry
->Data();
1781 NS_ENSURE_STATE(binding
);
1783 NS_IF_ADDREF(*result
= binding
->mDataFile
);
1788 nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry
*entry
, int32_t deltaSize
)
1790 LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1791 entry
->Key()->get(), deltaSize
));
1793 const int32_t DELTA_THRESHOLD
= 1<<14; // 16k
1795 // called to notify us of an impending change in the total size of the
1798 uint32_t oldSize
= entry
->DataSize();
1799 NS_ASSERTION(deltaSize
>= 0 || int32_t(oldSize
) + deltaSize
>= 0, "oops");
1800 uint32_t newSize
= int32_t(oldSize
) + deltaSize
;
1801 UpdateEntrySize(entry
, newSize
);
1803 mDeltaCounter
+= deltaSize
; // this may go negative
1805 if (mDeltaCounter
>= DELTA_THRESHOLD
)
1807 if (CacheSize() > mCacheCapacity
) {
1808 // the entry will overrun the cache capacity, doom the entry
1813 nsCacheService::DoomEntry(entry
);
1814 NS_ASSERTION(NS_SUCCEEDED(rv
), "DoomEntry() failed.");
1815 return NS_ERROR_ABORT
;
1818 mDeltaCounter
= 0; // reset counter
1825 nsOfflineCacheDevice::Visit(nsICacheVisitor
*visitor
)
1827 NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED
);
1829 // called to enumerate the offline cache.
1831 nsCOMPtr
<nsICacheDeviceInfo
> deviceInfo
=
1832 new nsOfflineCacheDeviceInfo(this);
1835 nsresult rv
= visitor
->VisitDevice(OFFLINE_CACHE_DEVICE_ID
, deviceInfo
,
1843 // SELECT * from moz_cache;
1845 nsOfflineCacheRecord rec
;
1846 nsRefPtr
<nsOfflineCacheEntryInfo
> info
= new nsOfflineCacheEntryInfo
;
1848 return NS_ERROR_OUT_OF_MEMORY
;
1851 // XXX may want to list columns explicitly
1852 nsCOMPtr
<mozIStorageStatement
> statement
;
1853 rv
= mDB
->CreateStatement(
1854 NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1855 getter_AddRefs(statement
));
1856 NS_ENSURE_SUCCESS(rv
, rv
);
1861 rv
= statement
->ExecuteStep(&hasRows
);
1862 if (NS_FAILED(rv
) || !hasRows
)
1865 statement
->GetSharedUTF8String(0, NULL
, &rec
.clientID
);
1866 statement
->GetSharedUTF8String(1, NULL
, &rec
.key
);
1867 statement
->GetSharedBlob(2, &rec
.metaDataLen
,
1868 (const uint8_t **) &rec
.metaData
);
1869 rec
.generation
= statement
->AsInt32(3);
1870 rec
.dataSize
= statement
->AsInt32(4);
1871 rec
.fetchCount
= statement
->AsInt32(5);
1872 rec
.lastFetched
= statement
->AsInt64(6);
1873 rec
.lastModified
= statement
->AsInt64(7);
1874 rec
.expirationTime
= statement
->AsInt64(8);
1877 rv
= visitor
->VisitEntry(OFFLINE_CACHE_DEVICE_ID
, info
, &keepGoing
);
1878 if (NS_FAILED(rv
) || !keepGoing
)
1882 info
->mRec
= nullptr;
1887 nsOfflineCacheDevice::EvictEntries(const char *clientID
)
1889 LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1890 clientID
? clientID
: ""));
1892 // called to evict all entries matching the given clientID.
1894 // need trigger to fire user defined function after a row is deleted
1895 // so we can delete the corresponding data file.
1896 EvictionObserver
evictionObserver(mDB
, mEvictionFunction
);
1898 nsCOMPtr
<mozIStorageStatement
> statement
;
1902 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
1903 getter_AddRefs(statement
));
1904 NS_ENSURE_SUCCESS(rv
, rv
);
1906 rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(clientID
));
1907 NS_ENSURE_SUCCESS(rv
, rv
);
1909 rv
= statement
->Execute();
1910 NS_ENSURE_SUCCESS(rv
, rv
);
1912 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
1913 getter_AddRefs(statement
));
1914 NS_ENSURE_SUCCESS(rv
, rv
);
1916 rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(clientID
));
1917 NS_ENSURE_SUCCESS(rv
, rv
);
1919 rv
= statement
->Execute();
1920 NS_ENSURE_SUCCESS(rv
, rv
);
1924 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
1925 getter_AddRefs(statement
));
1926 NS_ENSURE_SUCCESS(rv
, rv
);
1928 rv
= statement
->Execute();
1929 NS_ENSURE_SUCCESS(rv
, rv
);
1931 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
1932 getter_AddRefs(statement
));
1933 NS_ENSURE_SUCCESS(rv
, rv
);
1935 rv
= statement
->Execute();
1936 NS_ENSURE_SUCCESS(rv
, rv
);
1939 evictionObserver
.Apply();
1941 statement
= nullptr;
1942 // Also evict any namespaces associated with this clientID.
1945 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
1946 getter_AddRefs(statement
));
1947 NS_ENSURE_SUCCESS(rv
, rv
);
1949 rv
= statement
->BindUTF8StringByIndex(0, nsDependentCString(clientID
));
1950 NS_ENSURE_SUCCESS(rv
, rv
);
1954 rv
= mDB
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
1955 getter_AddRefs(statement
));
1956 NS_ENSURE_SUCCESS(rv
, rv
);
1959 rv
= statement
->Execute();
1960 NS_ENSURE_SUCCESS(rv
, rv
);
1966 nsOfflineCacheDevice::MarkEntry(const nsCString
&clientID
,
1967 const nsACString
&key
,
1970 LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1971 clientID
.get(), PromiseFlatCString(key
).get(), typeBits
));
1973 AutoResetStatement
statement(mStatement_MarkEntry
);
1974 nsresult rv
= statement
->BindInt32ByIndex(0, typeBits
);
1975 NS_ENSURE_SUCCESS(rv
, rv
);
1976 rv
= statement
->BindUTF8StringByIndex(1, clientID
);
1977 NS_ENSURE_SUCCESS(rv
, rv
);
1978 rv
= statement
->BindUTF8StringByIndex(2, key
);
1979 NS_ENSURE_SUCCESS(rv
, rv
);
1981 rv
= statement
->Execute();
1982 NS_ENSURE_SUCCESS(rv
, rv
);
1988 nsOfflineCacheDevice::UnmarkEntry(const nsCString
&clientID
,
1989 const nsACString
&key
,
1992 LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1993 clientID
.get(), PromiseFlatCString(key
).get(), typeBits
));
1995 AutoResetStatement
statement(mStatement_UnmarkEntry
);
1996 nsresult rv
= statement
->BindInt32ByIndex(0, typeBits
);
1997 NS_ENSURE_SUCCESS(rv
, rv
);
1998 rv
= statement
->BindUTF8StringByIndex(1, clientID
);
1999 NS_ENSURE_SUCCESS(rv
, rv
);
2000 rv
= statement
->BindUTF8StringByIndex(2, key
);
2001 NS_ENSURE_SUCCESS(rv
, rv
);
2003 rv
= statement
->Execute();
2004 NS_ENSURE_SUCCESS(rv
, rv
);
2006 // Remove the entry if it is now empty.
2008 EvictionObserver
evictionObserver(mDB
, mEvictionFunction
);
2010 AutoResetStatement
cleanupStatement(mStatement_CleanupUnmarked
);
2011 rv
= cleanupStatement
->BindUTF8StringByIndex(0, clientID
);
2012 NS_ENSURE_SUCCESS(rv
, rv
);
2013 rv
= cleanupStatement
->BindUTF8StringByIndex(1, key
);
2014 NS_ENSURE_SUCCESS(rv
, rv
);
2016 rv
= cleanupStatement
->Execute();
2017 NS_ENSURE_SUCCESS(rv
, rv
);
2019 evictionObserver
.Apply();
2025 nsOfflineCacheDevice::GetMatchingNamespace(const nsCString
&clientID
,
2026 const nsACString
&key
,
2027 nsIApplicationCacheNamespace
**out
)
2029 LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
2030 clientID
.get(), PromiseFlatCString(key
).get()));
2034 AutoResetStatement
statement(mStatement_FindNamespaceEntry
);
2036 rv
= statement
->BindUTF8StringByIndex(0, clientID
);
2037 NS_ENSURE_SUCCESS(rv
, rv
);
2038 rv
= statement
->BindUTF8StringByIndex(1, key
);
2039 NS_ENSURE_SUCCESS(rv
, rv
);
2042 rv
= statement
->ExecuteStep(&hasRows
);
2043 NS_ENSURE_SUCCESS(rv
, rv
);
2055 rv
= statement
->GetInt32(2, &itemType
);
2056 NS_ENSURE_SUCCESS(rv
, rv
);
2058 if (!found
|| itemType
> nsType
)
2062 rv
= statement
->GetUTF8String(0, nsSpec
);
2063 NS_ENSURE_SUCCESS(rv
, rv
);
2065 rv
= statement
->GetUTF8String(1, nsData
);
2066 NS_ENSURE_SUCCESS(rv
, rv
);
2071 rv
= statement
->ExecuteStep(&hasRows
);
2072 NS_ENSURE_SUCCESS(rv
, rv
);
2076 nsCOMPtr
<nsIApplicationCacheNamespace
> ns
=
2077 new nsApplicationCacheNamespace();
2079 return NS_ERROR_OUT_OF_MEMORY
;
2080 rv
= ns
->Init(nsType
, nsSpec
, nsData
);
2081 NS_ENSURE_SUCCESS(rv
, rv
);
2090 nsOfflineCacheDevice::CacheOpportunistically(const nsCString
&clientID
,
2091 const nsACString
&key
)
2093 // XXX: We should also be propagating this cache entry to other matching
2094 // caches. See bug 444807.
2096 return MarkEntry(clientID
, key
, nsIApplicationCache::ITEM_OPPORTUNISTIC
);
2100 nsOfflineCacheDevice::GetTypes(const nsCString
&clientID
,
2101 const nsACString
&key
,
2104 LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
2105 clientID
.get(), PromiseFlatCString(key
).get()));
2107 AutoResetStatement
statement(mStatement_GetTypes
);
2108 nsresult rv
= statement
->BindUTF8StringByIndex(0, clientID
);
2109 NS_ENSURE_SUCCESS(rv
, rv
);
2110 rv
= statement
->BindUTF8StringByIndex(1, key
);
2111 NS_ENSURE_SUCCESS(rv
, rv
);
2114 rv
= statement
->ExecuteStep(&hasRows
);
2115 NS_ENSURE_SUCCESS(rv
, rv
);
2118 return NS_ERROR_CACHE_KEY_NOT_FOUND
;
2120 *typeBits
= statement
->AsInt32(0);
2126 nsOfflineCacheDevice::GatherEntries(const nsCString
&clientID
,
2131 LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
2132 clientID
.get(), typeBits
));
2134 AutoResetStatement
statement(mStatement_GatherEntries
);
2135 nsresult rv
= statement
->BindUTF8StringByIndex(0, clientID
);
2136 NS_ENSURE_SUCCESS(rv
, rv
);
2138 rv
= statement
->BindInt32ByIndex(1, typeBits
);
2139 NS_ENSURE_SUCCESS(rv
, rv
);
2141 return RunSimpleQuery(mStatement_GatherEntries
, 0, count
, keys
);
2145 nsOfflineCacheDevice::AddNamespace(const nsCString
&clientID
,
2146 nsIApplicationCacheNamespace
*ns
)
2148 nsCString namespaceSpec
;
2149 nsresult rv
= ns
->GetNamespaceSpec(namespaceSpec
);
2150 NS_ENSURE_SUCCESS(rv
, rv
);
2153 rv
= ns
->GetData(data
);
2154 NS_ENSURE_SUCCESS(rv
, rv
);
2157 rv
= ns
->GetItemType(&itemType
);
2158 NS_ENSURE_SUCCESS(rv
, rv
);
2160 LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
2161 clientID
.get(), namespaceSpec
.get(), data
.get(), itemType
));
2163 AutoResetStatement
statement(mStatement_InsertNamespaceEntry
);
2165 rv
= statement
->BindUTF8StringByIndex(0, clientID
);
2166 NS_ENSURE_SUCCESS(rv
, rv
);
2168 rv
= statement
->BindUTF8StringByIndex(1, namespaceSpec
);
2169 NS_ENSURE_SUCCESS(rv
, rv
);
2171 rv
= statement
->BindUTF8StringByIndex(2, data
);
2172 NS_ENSURE_SUCCESS(rv
, rv
);
2174 rv
= statement
->BindInt32ByIndex(3, itemType
);
2175 NS_ENSURE_SUCCESS(rv
, rv
);
2177 rv
= statement
->Execute();
2178 NS_ENSURE_SUCCESS(rv
, rv
);
2184 nsOfflineCacheDevice::GetUsage(const nsACString
&clientID
,
2187 LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2188 PromiseFlatCString(clientID
).get()));
2192 AutoResetStatement
statement(mStatement_ApplicationCacheSize
);
2194 nsresult rv
= statement
->BindUTF8StringByIndex(0, clientID
);
2195 NS_ENSURE_SUCCESS(rv
, rv
);
2198 rv
= statement
->ExecuteStep(&hasRows
);
2199 NS_ENSURE_SUCCESS(rv
, rv
);
2204 *usage
= static_cast<uint32_t>(statement
->AsInt32(0));
2210 nsOfflineCacheDevice::GetGroups(uint32_t *count
,
2213 LOG(("nsOfflineCacheDevice::GetGroups"));
2215 return RunSimpleQuery(mStatement_EnumerateGroups
, 0, count
, keys
);
2219 nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count
,
2222 LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
2224 return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder
, 0, count
, keys
);
2228 nsOfflineCacheDevice::IsLocked(const nsACString
&key
)
2230 return mLockedEntries
.GetEntry(key
);
2234 nsOfflineCacheDevice::Lock(const nsACString
&key
)
2236 mLockedEntries
.PutEntry(key
);
2240 nsOfflineCacheDevice::Unlock(const nsACString
&key
)
2242 mLockedEntries
.RemoveEntry(key
);
2246 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement
* statement
,
2247 uint32_t resultIndex
,
2252 nsresult rv
= statement
->ExecuteStep(&hasRows
);
2253 NS_ENSURE_SUCCESS(rv
, rv
);
2255 nsTArray
<nsCString
> valArray
;
2259 valArray
.AppendElement(
2260 nsDependentCString(statement
->AsSharedUTF8String(resultIndex
, &length
)));
2262 rv
= statement
->ExecuteStep(&hasRows
);
2263 NS_ENSURE_SUCCESS(rv
, rv
);
2266 *count
= valArray
.Length();
2267 char **ret
= static_cast<char **>(NS_Alloc(*count
* sizeof(char*)));
2268 if (!ret
) return NS_ERROR_OUT_OF_MEMORY
;
2270 for (uint32_t i
= 0; i
< *count
; i
++) {
2271 ret
[i
] = NS_strdup(valArray
[i
].get());
2273 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i
, ret
);
2274 return NS_ERROR_OUT_OF_MEMORY
;
2284 nsOfflineCacheDevice::CreateApplicationCache(const nsACString
&group
,
2285 nsIApplicationCache
**out
)
2290 // Some characters are special in the clientID. Escape the groupID
2291 // before putting it in to the client key.
2292 if (!NS_Escape(nsCString(group
), clientID
, url_Path
)) {
2293 return NS_ERROR_OUT_OF_MEMORY
;
2296 PRTime now
= PR_Now();
2298 // Include the timestamp to guarantee uniqueness across runs, and
2299 // the gNextTemporaryClientID for uniqueness within a second.
2300 clientID
.Append(nsPrintfCString("|%016lld|%d",
2301 now
/ PR_USEC_PER_SEC
,
2302 gNextTemporaryClientID
++));
2304 nsCOMPtr
<nsIApplicationCache
> cache
= new nsApplicationCache(this,
2308 return NS_ERROR_OUT_OF_MEMORY
;
2310 nsCOMPtr
<nsIWeakReference
> weak
= do_GetWeakReference(cache
);
2312 return NS_ERROR_OUT_OF_MEMORY
;
2314 mCaches
.Put(clientID
, weak
);
2322 nsOfflineCacheDevice::GetApplicationCache(const nsACString
&clientID
,
2323 nsIApplicationCache
**out
)
2327 nsCOMPtr
<nsIApplicationCache
> cache
;
2330 if (mCaches
.Get(clientID
, getter_AddRefs(weak
)))
2331 cache
= do_QueryReferent(weak
);
2336 nsresult rv
= GetGroupForCache(clientID
, group
);
2337 NS_ENSURE_SUCCESS(rv
, rv
);
2339 if (group
.IsEmpty()) {
2343 cache
= new nsApplicationCache(this, group
, clientID
);
2344 weak
= do_GetWeakReference(cache
);
2346 return NS_ERROR_OUT_OF_MEMORY
;
2348 mCaches
.Put(clientID
, weak
);
2357 nsOfflineCacheDevice::GetActiveCache(const nsACString
&group
,
2358 nsIApplicationCache
**out
)
2362 nsCString
*clientID
;
2363 if (mActiveCachesByGroup
.Get(group
, &clientID
))
2364 return GetApplicationCache(*clientID
, out
);
2370 nsOfflineCacheDevice::DeactivateGroup(const nsACString
&group
)
2372 nsCString
*active
= nullptr;
2374 AutoResetStatement
statement(mStatement_DeactivateGroup
);
2375 nsresult rv
= statement
->BindUTF8StringByIndex(0, group
);
2376 NS_ENSURE_SUCCESS(rv
, rv
);
2378 rv
= statement
->Execute();
2379 NS_ENSURE_SUCCESS(rv
, rv
);
2381 if (mActiveCachesByGroup
.Get(group
, &active
))
2383 mActiveCaches
.RemoveEntry(*active
);
2384 mActiveCachesByGroup
.Remove(group
);
2392 nsOfflineCacheDevice::DiscardByAppId(int32_t appID
, bool browserEntriesOnly
)
2396 nsAutoCString jaridsuffix
;
2397 jaridsuffix
.Append('%');
2398 rv
= AppendJARIdentifier(jaridsuffix
, appID
, browserEntriesOnly
);
2399 NS_ENSURE_SUCCESS(rv
, rv
);
2401 AutoResetStatement
statement(mStatement_EnumerateApps
);
2402 rv
= statement
->BindUTF8StringByIndex(0, jaridsuffix
);
2403 NS_ENSURE_SUCCESS(rv
, rv
);
2406 rv
= statement
->ExecuteStep(&hasRows
);
2407 NS_ENSURE_SUCCESS(rv
, rv
);
2410 nsAutoCString group
;
2411 rv
= statement
->GetUTF8String(0, group
);
2412 NS_ENSURE_SUCCESS(rv
, rv
);
2415 rv
= statement
->GetUTF8String(1, clientID
);
2416 NS_ENSURE_SUCCESS(rv
, rv
);
2418 nsCOMPtr
<nsIRunnable
> ev
=
2419 new nsOfflineCacheDiscardCache(this, group
, clientID
);
2421 rv
= nsCacheService::DispatchToCacheIOThread(ev
);
2422 NS_ENSURE_SUCCESS(rv
, rv
);
2424 rv
= statement
->ExecuteStep(&hasRows
);
2425 NS_ENSURE_SUCCESS(rv
, rv
);
2428 if (!browserEntriesOnly
) {
2429 // If deleting app, delete any 'inBrowserElement' entries too
2430 rv
= DiscardByAppId(appID
, true);
2431 NS_ENSURE_SUCCESS(rv
, rv
);
2438 nsOfflineCacheDevice::CanUseCache(nsIURI
*keyURI
,
2439 const nsACString
&clientID
,
2440 nsILoadContext
*loadContext
)
2442 if (!mActiveCaches
.Contains(clientID
))
2445 nsAutoCString groupID
;
2446 nsresult rv
= GetGroupForCache(clientID
, groupID
);
2447 NS_ENSURE_SUCCESS(rv
, false);
2449 nsCOMPtr
<nsIURI
> groupURI
;
2450 rv
= NS_NewURI(getter_AddRefs(groupURI
), groupID
);
2454 // When we are choosing an initial cache to load the top
2455 // level document from, the URL of that document must have
2456 // the same origin as the manifest, according to the spec.
2457 // The following check is here because explicit, fallback
2458 // and dynamic entries might have origin different from the
2460 if (!NS_SecurityCompareURIs(keyURI
, groupURI
,
2461 GetStrictFileOriginPolicy()))
2464 // Get extended origin attributes
2465 uint32_t appId
= NECKO_NO_APP_ID
;
2466 bool isInBrowserElement
= false;
2469 rv
= loadContext
->GetAppId(&appId
);
2470 NS_ENSURE_SUCCESS(rv
, false);
2472 rv
= loadContext
->GetIsInBrowserElement(&isInBrowserElement
);
2473 NS_ENSURE_SUCCESS(rv
, false);
2476 // Check the groupID we found is equal to groupID based
2477 // on the load context demanding load from app cache.
2478 // This is check of extended origin.
2479 nsAutoCString demandedGroupID
;
2480 rv
= BuildApplicationCacheGroupID(groupURI
, appId
, isInBrowserElement
,
2482 NS_ENSURE_SUCCESS(rv
, false);
2484 if (groupID
!= demandedGroupID
)
2492 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString
&key
,
2493 nsILoadContext
*loadContext
,
2494 nsIApplicationCache
**out
)
2498 nsCOMPtr
<nsIURI
> keyURI
;
2499 nsresult rv
= NS_NewURI(getter_AddRefs(keyURI
), key
);
2500 NS_ENSURE_SUCCESS(rv
, rv
);
2502 // First try to find a matching cache entry.
2503 AutoResetStatement
statement(mStatement_FindClient
);
2504 rv
= statement
->BindUTF8StringByIndex(0, key
);
2505 NS_ENSURE_SUCCESS(rv
, rv
);
2508 rv
= statement
->ExecuteStep(&hasRows
);
2509 NS_ENSURE_SUCCESS(rv
, rv
);
2513 rv
= statement
->GetInt32(1, &itemType
);
2514 NS_ENSURE_SUCCESS(rv
, rv
);
2516 if (!(itemType
& nsIApplicationCache::ITEM_FOREIGN
)) {
2517 nsAutoCString clientID
;
2518 rv
= statement
->GetUTF8String(0, clientID
);
2519 NS_ENSURE_SUCCESS(rv
, rv
);
2521 if (CanUseCache(keyURI
, clientID
, loadContext
)) {
2522 return GetApplicationCache(clientID
, out
);
2526 rv
= statement
->ExecuteStep(&hasRows
);
2527 NS_ENSURE_SUCCESS(rv
, rv
);
2530 // OK, we didn't find an exact match. Search for a client with a
2531 // matching namespace.
2533 AutoResetStatement
nsstatement(mStatement_FindClientByNamespace
);
2535 rv
= nsstatement
->BindUTF8StringByIndex(0, key
);
2536 NS_ENSURE_SUCCESS(rv
, rv
);
2538 rv
= nsstatement
->ExecuteStep(&hasRows
);
2539 NS_ENSURE_SUCCESS(rv
, rv
);
2544 rv
= nsstatement
->GetInt32(1, &itemType
);
2545 NS_ENSURE_SUCCESS(rv
, rv
);
2547 // Don't associate with a cache based solely on a whitelist entry
2548 if (!(itemType
& nsIApplicationCacheNamespace::NAMESPACE_BYPASS
)) {
2549 nsAutoCString clientID
;
2550 rv
= nsstatement
->GetUTF8String(0, clientID
);
2551 NS_ENSURE_SUCCESS(rv
, rv
);
2553 if (CanUseCache(keyURI
, clientID
, loadContext
)) {
2554 return GetApplicationCache(clientID
, out
);
2558 rv
= nsstatement
->ExecuteStep(&hasRows
);
2559 NS_ENSURE_SUCCESS(rv
, rv
);
2566 nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache
* cache
,
2567 const nsACString
&key
)
2569 NS_ENSURE_ARG_POINTER(cache
);
2573 nsAutoCString clientID
;
2574 rv
= cache
->GetClientID(clientID
);
2575 NS_ENSURE_SUCCESS(rv
, rv
);
2577 return CacheOpportunistically(clientID
, key
);
2581 nsOfflineCacheDevice::ActivateCache(const nsCSubstring
&group
,
2582 const nsCSubstring
&clientID
)
2584 AutoResetStatement
statement(mStatement_ActivateClient
);
2585 nsresult rv
= statement
->BindUTF8StringByIndex(0, group
);
2586 NS_ENSURE_SUCCESS(rv
, rv
);
2587 rv
= statement
->BindUTF8StringByIndex(1, clientID
);
2588 NS_ENSURE_SUCCESS(rv
, rv
);
2589 rv
= statement
->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
2590 NS_ENSURE_SUCCESS(rv
, rv
);
2592 rv
= statement
->Execute();
2593 NS_ENSURE_SUCCESS(rv
, rv
);
2596 if (mActiveCachesByGroup
.Get(group
, &active
))
2598 mActiveCaches
.RemoveEntry(*active
);
2599 mActiveCachesByGroup
.Remove(group
);
2603 if (!clientID
.IsEmpty())
2605 mActiveCaches
.PutEntry(clientID
);
2606 mActiveCachesByGroup
.Put(group
, new nsCString(clientID
));
2613 nsOfflineCacheDevice::IsActiveCache(const nsCSubstring
&group
,
2614 const nsCSubstring
&clientID
)
2616 nsCString
*active
= nullptr;
2617 return mActiveCachesByGroup
.Get(group
, &active
) && *active
== clientID
;
2621 * Preference accessors
2625 nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile
*parentDir
)
2629 NS_ERROR("cannot switch cache directory once initialized");
2635 mCacheDirectory
= nullptr;
2639 // ensure parent directory exists
2640 nsresult rv
= EnsureDir(parentDir
);
2643 NS_WARNING("unable to create parent directory");
2647 mBaseDirectory
= parentDir
;
2649 // cache dir may not exist, but that's ok
2650 nsCOMPtr
<nsIFile
> dir
;
2651 rv
= parentDir
->Clone(getter_AddRefs(dir
));
2654 rv
= dir
->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2658 mCacheDirectory
= do_QueryInterface(dir
);
2662 nsOfflineCacheDevice::SetCapacity(uint32_t capacity
)
2664 mCacheCapacity
= capacity
* 1024;
2668 nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache
* aAppCache
)
2673 mAutoShutdown
= false;
2677 nsRefPtr
<nsCacheService
> cacheService
= nsCacheService::GlobalInstance();
2678 cacheService
->RemoveCustomOfflineDevice(this);
2680 nsAutoCString clientID
;
2681 aAppCache
->GetClientID(clientID
);
2682 mCaches
.Remove(clientID
);