Bug 853360 - Implement the coneGain part of the AudioPannerNode. r=roc
[gecko.git] / netwerk / cache / nsDiskCacheDeviceSQL.cpp
blob88a1ff9bed3c8267717bfbf9b5584b4e4fb93727
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"
10 #include "nsCache.h"
11 #include "nsDiskCache.h"
12 #include "nsDiskCacheDeviceSQL.h"
13 #include "nsCacheService.h"
14 #include "nsApplicationCache.h"
16 #include "nsNetCID.h"
17 #include "nsNetUtil.h"
18 #include "nsAutoPtr.h"
19 #include "nsEscape.h"
20 #include "nsIPrefBranch.h"
21 #include "nsIPrefService.h"
22 #include "nsString.h"
23 #include "nsPrintfCString.h"
24 #include "nsCRT.h"
25 #include "nsArrayUtils.h"
26 #include "nsIArray.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"
42 #include "sqlite3.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 /*****************************************************************************
55 * helpers
58 static nsresult
59 EnsureDir(nsIFile *dir)
61 bool exists;
62 nsresult rv = dir->Exists(&exists);
63 if (NS_SUCCEEDED(rv) && !exists)
64 rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
65 return rv;
68 static bool
69 DecomposeCacheEntryKey(const nsCString *fullKey,
70 const char **cid,
71 const char **key,
72 nsCString &buf)
74 buf = *fullKey;
76 int32_t colon = buf.FindChar(':');
77 if (colon == kNotFound)
79 NS_ERROR("Invalid key");
80 return false;
82 buf.SetCharAt('\0', colon);
84 *cid = buf.get();
85 *key = buf.get() + colon + 1;
87 return true;
90 class AutoResetStatement
92 public:
93 AutoResetStatement(mozIStorageStatement *s)
94 : mStatement(s) {}
95 ~AutoResetStatement() { mStatement->Reset(); }
96 mozIStorageStatement *operator->() { return mStatement; }
97 private:
98 mozIStorageStatement *mStatement;
101 class EvictionObserver
103 public:
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);"
113 " END;"));
114 mEvictionFunction->Reset();
117 ~EvictionObserver()
119 mDB->ExecuteSimpleSQL(
120 NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
121 mEvictionFunction->Reset();
124 void Apply() { return mEvictionFunction->Apply(); }
126 private:
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
140 * to be revised.
142 static uint64_t
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
157 static nsresult
158 GetCacheDataFile(nsIFile *cacheDir, const char *key,
159 int generation, nsCOMPtr<nsIFile> &file)
161 cacheDir->Clone(getter_AddRefs(file));
162 if (!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);
170 hash >>= 8;
172 file->AppendNative(nsPrintfCString("%X", dir1));
173 file->AppendNative(nsPrintfCString("%X", dir2));
175 char leaf[64];
176 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
177 return file->AppendNative(nsDependentCString(leaf));
180 NS_IMETHODIMP
181 nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
183 LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
185 *_retval = nullptr;
187 uint32_t numEntries;
188 nsresult rv = values->GetNumEntries(&numEntries);
189 NS_ENSURE_SUCCESS(rv, rv);
190 NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
192 uint32_t valueLen;
193 const char *clientID = values->AsSharedUTF8String(0, &valueLen);
194 const char *key = values->AsSharedUTF8String(1, &valueLen);
195 nsAutoCString fullKey(clientID);
196 fullKey.AppendLiteral(":");
197 fullKey.Append(key);
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));
203 return NS_OK;
206 nsCOMPtr<nsIFile> file;
207 rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
208 generation, file);
209 if (NS_FAILED(rv))
211 LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
212 key, generation, rv));
213 return rv;
216 mItems.AppendObject(file);
218 return NS_OK;
221 void
222 nsOfflineCacheEvictionFunction::Apply()
224 LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
226 for (int32_t i = 0; i < mItems.Count(); i++) {
227 #if defined(PR_LOGGING)
228 nsAutoCString path;
229 mItems[i]->GetNativePath(path);
230 LOG((" removing %s\n", path.get()));
231 #endif
233 mItems[i]->Remove(false);
236 Reset();
239 class nsOfflineCacheDiscardCache : public nsRunnable
241 public:
242 nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device,
243 nsCString &group,
244 nsCString &clientID)
245 : mDevice(device)
246 , mGroup(group)
247 , mClientID(clientID)
251 NS_IMETHOD Run()
253 if (mDevice->IsActiveCache(mGroup, mClientID))
255 mDevice->DeactivateGroup(mGroup);
258 return mDevice->EvictEntries(mClientID.get());
261 private:
262 nsRefPtr<nsOfflineCacheDevice> mDevice;
263 nsCString mGroup;
264 nsCString mClientID;
267 /******************************************************************************
268 * nsOfflineCacheDeviceInfo
271 class nsOfflineCacheDeviceInfo MOZ_FINAL : public nsICacheDeviceInfo
273 public:
274 NS_DECL_ISUPPORTS
275 NS_DECL_NSICACHEDEVICEINFO
277 nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
278 : mDevice(device)
281 private:
282 nsOfflineCacheDevice* mDevice;
285 NS_IMPL_ISUPPORTS1(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
287 NS_IMETHODIMP
288 nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
290 *aDescription = NS_strdup("Offline cache device");
291 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
294 NS_IMETHODIMP
295 nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
297 nsAutoCString buffer;
298 buffer.AssignLiteral(" <tr>\n"
299 " <th>Cache Directory:</th>\n"
300 " <td>");
301 nsIFile *cacheDir = mDevice->CacheDirectory();
302 if (!cacheDir)
303 return NS_OK;
305 nsAutoString path;
306 nsresult rv = cacheDir->GetPath(path);
307 if (NS_SUCCEEDED(rv))
308 AppendUTF16toUTF8(path, buffer);
309 else
310 buffer.AppendLiteral("directory unavailable");
312 buffer.AppendLiteral("</td>\n"
313 " </tr>\n");
315 *usageReport = ToNewCString(buffer);
316 if (!*usageReport)
317 return NS_ERROR_OUT_OF_MEMORY;
319 return NS_OK;
322 NS_IMETHODIMP
323 nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
325 *aEntryCount = mDevice->EntryCount();
326 return NS_OK;
329 NS_IMETHODIMP
330 nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
332 *aTotalSize = mDevice->CacheSize();
333 return NS_OK;
336 NS_IMETHODIMP
337 nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
339 *aMaximumSize = mDevice->CacheCapacity();
340 return NS_OK;
343 /******************************************************************************
344 * nsOfflineCacheBinding
347 class nsOfflineCacheBinding MOZ_FINAL : public nsISupports
349 public:
350 NS_DECL_ISUPPORTS
352 static nsOfflineCacheBinding *
353 Create(nsIFile *cacheDir, const nsCString *key, int generation);
355 enum { FLAG_NEW_ENTRY = 1 };
357 nsCOMPtr<nsIFile> mDataFile;
358 int mGeneration;
359 int mFlags;
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,
371 int generation)
373 nsCOMPtr<nsIFile> file;
374 cacheDir->Clone(getter_AddRefs(file));
375 if (!file)
376 return nullptr;
378 nsAutoCString keyBuf;
379 const char *cid, *key;
380 if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
381 return nullptr;
383 uint64_t hash = DCacheHash(key);
385 uint32_t dir1 = (uint32_t) (hash & 0x0F);
386 uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
388 hash >>= 8;
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);
398 nsresult rv;
399 char leaf[64];
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));
410 if (NS_FAILED(rv))
411 return nullptr;
412 rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
413 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
414 return nullptr;
415 if (NS_SUCCEEDED(rv))
416 break;
419 else
421 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
422 rv = file->AppendNative(nsDependentCString(leaf));
423 if (NS_FAILED(rv))
424 return nullptr;
427 nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
428 if (!binding)
429 return nullptr;
431 binding->mDataFile.swap(file);
432 binding->mGeneration = generation;
433 binding->mFlags = 0;
434 return binding;
437 /******************************************************************************
438 * nsOfflineCacheRecord
441 struct nsOfflineCacheRecord
443 const char *clientID;
444 const char *key;
445 const uint8_t *metaData;
446 uint32_t metaDataLen;
447 int32_t generation;
448 int32_t dataSize;
449 int32_t fetchCount;
450 int64_t lastFetched;
451 int64_t lastModified;
452 int64_t expirationTime;
455 static nsCacheEntry *
456 CreateCacheEntry(nsOfflineCacheDevice *device,
457 const nsCString *fullKey,
458 const nsOfflineCacheRecord &rec)
460 nsCacheEntry *entry;
462 if (device->IsLocked(*fullKey)) {
463 return nullptr;
466 nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
467 nsICache::STREAM_BASED,
468 nsICache::STORE_OFFLINE,
469 device, &entry);
470 if (NS_FAILED(rv))
471 return nullptr;
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");
483 if (info) {
484 nsCOMPtr<nsISupports> infoObj;
485 rv = NS_DeserializeObject(nsDependentCString(info),
486 getter_AddRefs(infoObj));
487 if (NS_FAILED(rv)) {
488 delete entry;
489 return nullptr;
491 entry->SetSecurityInfo(infoObj);
494 // create a binding object for this entry
495 nsOfflineCacheBinding *binding =
496 nsOfflineCacheBinding::Create(device->CacheDirectory(),
497 fullKey,
498 rec.generation);
499 if (!binding)
501 delete entry;
502 return nullptr;
504 entry->SetData(binding);
506 return entry;
510 /******************************************************************************
511 * nsOfflineCacheEntryInfo
514 class nsOfflineCacheEntryInfo MOZ_FINAL : public nsICacheEntryInfo
516 public:
517 NS_DECL_ISUPPORTS
518 NS_DECL_NSICACHEENTRYINFO
520 nsOfflineCacheRecord *mRec;
523 NS_IMPL_ISUPPORTS1(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
525 NS_IMETHODIMP
526 nsOfflineCacheEntryInfo::GetClientID(char **result)
528 *result = NS_strdup(mRec->clientID);
529 return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
532 NS_IMETHODIMP
533 nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
535 *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
536 return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
539 NS_IMETHODIMP
540 nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
542 clientKey.Assign(mRec->key);
543 return NS_OK;
546 NS_IMETHODIMP
547 nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
549 *aFetchCount = mRec->fetchCount;
550 return NS_OK;
553 NS_IMETHODIMP
554 nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
556 *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
557 return NS_OK;
560 NS_IMETHODIMP
561 nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
563 *aLastModified = SecondsFromPRTime(mRec->lastModified);
564 return NS_OK;
567 NS_IMETHODIMP
568 nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
570 *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
571 return NS_OK;
574 NS_IMETHODIMP
575 nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
577 *aStreamBased = true;
578 return NS_OK;
581 NS_IMETHODIMP
582 nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
584 *aDataSize = mRec->dataSize;
585 return NS_OK;
589 /******************************************************************************
590 * nsApplicationCacheNamespace
593 NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
595 NS_IMETHODIMP
596 nsApplicationCacheNamespace::Init(uint32_t itemType,
597 const nsACString &namespaceSpec,
598 const nsACString &data)
600 mItemType = itemType;
601 mNamespaceSpec = namespaceSpec;
602 mData = data;
603 return NS_OK;
606 NS_IMETHODIMP
607 nsApplicationCacheNamespace::GetItemType(uint32_t *out)
609 *out = mItemType;
610 return NS_OK;
613 NS_IMETHODIMP
614 nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
616 out = mNamespaceSpec;
617 return NS_OK;
620 NS_IMETHODIMP
621 nsApplicationCacheNamespace::GetData(nsACString &out)
623 out = mData;
624 return NS_OK;
627 /******************************************************************************
628 * nsApplicationCache
631 NS_IMPL_ISUPPORTS2(nsApplicationCache,
632 nsIApplicationCache,
633 nsISupportsWeakReference)
635 nsApplicationCache::nsApplicationCache()
636 : mDevice(nullptr)
637 , mValid(true)
641 nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
642 const nsACString &group,
643 const nsACString &clientID)
644 : mDevice(device)
645 , mGroup(group)
646 , mClientID(clientID)
647 , mValid(true)
651 nsApplicationCache::~nsApplicationCache()
653 if (!mDevice)
654 return;
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))
660 Discard();
663 void
664 nsApplicationCache::MarkInvalid()
666 mValid = false;
669 NS_IMETHODIMP
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);
676 mGroup = groupId;
677 mClientID = clientId;
678 return NS_OK;
681 NS_IMETHODIMP
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);
691 return NS_OK;
694 NS_IMETHODIMP
695 nsApplicationCache::GetGroupID(nsACString &out)
697 out = mGroup;
698 return NS_OK;
701 NS_IMETHODIMP
702 nsApplicationCache::GetClientID(nsACString &out)
704 out = mClientID;
705 return NS_OK;
708 NS_IMETHODIMP
709 nsApplicationCache::GetProfileDirectory(nsIFile **out)
711 if (mDevice->BaseDirectory())
712 NS_ADDREF(*out = mDevice->BaseDirectory());
713 else
714 *out = nullptr;
716 return NS_OK;
719 NS_IMETHODIMP
720 nsApplicationCache::GetActive(bool *out)
722 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
724 *out = mDevice->IsActiveCache(mGroup, mClientID);
725 return NS_OK;
728 NS_IMETHODIMP
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))
737 mDevice = nullptr;
739 return NS_OK;
742 NS_IMETHODIMP
743 nsApplicationCache::Discard()
745 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
746 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
748 mValid = false;
750 nsRefPtr<nsIRunnable> ev =
751 new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
752 nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
753 return rv;
756 NS_IMETHODIMP
757 nsApplicationCache::MarkEntry(const nsACString &key,
758 uint32_t typeBits)
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);
767 NS_IMETHODIMP
768 nsApplicationCache::UnmarkEntry(const nsACString &key,
769 uint32_t typeBits)
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);
777 NS_IMETHODIMP
778 nsApplicationCache::GetTypes(const nsACString &key,
779 uint32_t *typeBits)
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);
787 NS_IMETHODIMP
788 nsApplicationCache::GatherEntries(uint32_t typeBits,
789 uint32_t * count,
790 char *** keys)
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);
798 NS_IMETHODIMP
799 nsApplicationCache::AddNamespaces(nsIArray *namespaces)
801 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
802 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
804 if (!namespaces)
805 return NS_OK;
807 mozStorageTransaction transaction(mDevice->mDB, false);
809 uint32_t length;
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);
816 if (ns) {
817 rv = mDevice->AddNamespace(mClientID, ns);
818 NS_ENSURE_SUCCESS(rv, rv);
822 rv = transaction.Commit();
823 NS_ENSURE_SUCCESS(rv, rv);
825 return NS_OK;
828 NS_IMETHODIMP
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);
839 NS_IMETHODIMP
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 /******************************************************************************
849 * nsCloseDBEvent
850 *****************************************************************************/
852 class nsCloseDBEvent : public nsRunnable {
853 public:
854 nsCloseDBEvent(mozIStorageConnection *aDB)
856 mDB = aDB;
859 NS_IMETHOD Run()
861 mDB->Close();
862 return NS_OK;
865 protected:
866 virtual ~nsCloseDBEvent() {}
868 private:
869 nsCOMPtr<mozIStorageConnection> mDB;
874 /******************************************************************************
875 * nsOfflineCacheDevice
878 NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheDevice)
880 nsOfflineCacheDevice::nsOfflineCacheDevice()
881 : mDB(nullptr)
882 , mCacheCapacity(0)
883 , mDeltaCounter(0)
884 , mAutoShutdown(false)
888 /* static */
889 bool
890 nsOfflineCacheDevice::GetStrictFileOriginPolicy()
892 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
894 bool retval;
895 if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
896 return retval;
898 // As default value use true (be more strict)
899 return true;
902 uint32_t
903 nsOfflineCacheDevice::CacheSize()
905 AutoResetStatement statement(mStatement_CacheSize);
907 bool hasRows;
908 nsresult rv = statement->ExecuteStep(&hasRows);
909 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
911 return (uint32_t) statement->AsInt32(0);
914 uint32_t
915 nsOfflineCacheDevice::EntryCount()
917 AutoResetStatement statement(mStatement_EntryCount);
919 bool hasRows;
920 nsresult rv = statement->ExecuteStep(&hasRows);
921 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
923 return (uint32_t) statement->AsInt32(0);
926 nsresult
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;
942 if (serializable) {
943 nsCString info;
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);
969 nsresult rv;
970 rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
971 nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
972 if (NS_FAILED(tmp)) {
973 rv = tmp;
975 tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
976 if (NS_FAILED(tmp)) {
977 rv = tmp;
979 tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
980 if (NS_FAILED(tmp)) {
981 rv = tmp;
983 tmp = statement->BindInt64ByIndex(4, rec.lastModified);
984 if (NS_FAILED(tmp)) {
985 rv = tmp;
987 tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
988 if (NS_FAILED(tmp)) {
989 rv = tmp;
991 tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
992 if (NS_FAILED(tmp)) {
993 rv = tmp;
995 tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
996 if (NS_FAILED(tmp)) {
997 rv = tmp;
999 NS_ENSURE_SUCCESS(rv, rv);
1001 bool hasRows;
1002 rv = statement->ExecuteStep(&hasRows);
1003 NS_ENSURE_SUCCESS(rv, rv);
1005 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1006 return rv;
1009 nsresult
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)) {
1023 rv = tmp;
1025 tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
1026 if (NS_FAILED(tmp)) {
1027 rv = tmp;
1029 NS_ENSURE_SUCCESS(rv, rv);
1031 bool hasRows;
1032 rv = statement->ExecuteStep(&hasRows);
1033 NS_ENSURE_SUCCESS(rv, rv);
1035 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1036 return rv;
1039 nsresult
1040 nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
1042 if (deleteData)
1044 nsresult rv = DeleteData(entry);
1045 if (NS_FAILED(rv))
1046 return rv;
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);
1062 bool hasRows;
1063 rv = statement->ExecuteStep(&hasRows);
1064 NS_ENSURE_SUCCESS(rv, rv);
1066 NS_ASSERTION(!hasRows, "DELETE should not result in output");
1067 return rv;
1070 nsresult
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
1085 // type. Boo-urns!
1086 struct StatementSql {
1087 nsCOMPtr<mozIStorageStatement> &statement;
1088 const char *sql;
1089 StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
1090 statement (aStatement), sql (aSql) {}
1093 nsresult
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" ?
1128 // build the table
1130 // "Generation" is the data file generation number.
1132 rv = mDB->ExecuteSimpleSQL(
1133 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1134 " ClientID TEXT,\n"
1135 " Key TEXT,\n"
1136 " MetaData BLOB,\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"
1144 ");\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"
1158 ");\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
1166 // tables.
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"
1173 " ClientID TEXT,\n"
1174 " NameSpace TEXT,\n"
1175 " Data TEXT,\n"
1176 " ItemType INTEGER\n"
1177 ");\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);
1264 return NS_OK;
1267 namespace {
1269 nsresult
1270 GetGroupForCache(const nsCSubstring &clientID, nsCString &group)
1272 group.Assign(clientID);
1273 group.Truncate(group.FindChar('|'));
1274 NS_UnescapeURL(group);
1276 return NS_OK;
1279 nsresult
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');
1287 return NS_OK;
1290 nsresult
1291 GetJARIdentifier(nsIURI *aURI,
1292 uint32_t appId, bool isInBrowserElement,
1293 nsACString &_result)
1295 _result.Truncate();
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
1300 // not be found).
1301 if (!isInBrowserElement && appId == NECKO_NO_APP_ID)
1302 return NS_OK;
1304 // This load context has some special attributes, create a jar identifier
1305 return AppendJARIdentifier(_result, appId, isInBrowserElement);
1308 } // anon namespace
1310 // static
1311 nsresult
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);
1334 return NS_OK;
1337 nsresult
1338 nsOfflineCacheDevice::InitActiveCaches()
1340 mCaches.Init();
1341 mActiveCachesByGroup.Init();
1343 mActiveCaches.Init(5);
1345 mLockedEntries.Init(64);
1347 AutoResetStatement statement(mStatement_EnumerateGroups);
1349 bool hasRows;
1350 nsresult rv = statement->ExecuteStep(&hasRows);
1351 NS_ENSURE_SUCCESS(rv, rv);
1353 while (hasRows)
1355 nsAutoCString group;
1356 statement->GetUTF8String(0, group);
1357 nsCString clientID;
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);
1367 return NS_OK;
1370 /* static */
1371 PLDHashOperator
1372 nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key,
1373 nsIWeakReference *weakRef,
1374 void *ctx)
1376 nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(weakRef);
1377 if (obj)
1379 nsApplicationCache *appCache = static_cast<nsApplicationCache*>(obj.get());
1380 appCache->MarkInvalid();
1383 return PL_DHASH_NEXT;
1386 nsresult
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)"));
1405 if (NS_FAILED(rv))
1406 NS_WARNING("Failed to clean up unused application caches.");
1407 else
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)"));
1418 if (NS_FAILED(rv))
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;
1450 if (mInitThread)
1451 mInitThread->IsOnCurrentThread(&isOnCurrentThread);
1453 if (!isOnCurrentThread) {
1454 nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
1456 if (ev) {
1457 mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1460 else {
1461 mDB->Close();
1464 mDB = nullptr;
1465 mInitThread = nullptr;
1467 return NS_OK;
1470 const char *
1471 nsOfflineCacheDevice::GetDeviceID()
1473 return OFFLINE_CACHE_DEVICE_ID;
1476 nsCacheEntry *
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))
1488 return nullptr;
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);
1497 bool hasRows;
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",
1513 rec.metaDataLen,
1514 rec.generation,
1515 rec.dataSize,
1516 rec.fetchCount,
1517 rec.lastFetched,
1518 rec.lastModified,
1519 rec.expirationTime));
1521 nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
1523 if (entry)
1525 // make sure that the data file exists
1526 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
1527 bool isFile;
1528 rv = binding->mDataFile->IsFile(&isFile);
1529 if (NS_FAILED(rv) || !isFile)
1531 DeleteEntry(entry, false);
1532 delete entry;
1533 return nullptr;
1536 // lock the entry
1537 Lock(*fullKey);
1540 return entry;
1543 nsresult
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
1559 // on disk.
1560 DeleteData(entry);
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
1568 // BindEntry().
1570 LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
1571 UpdateEntry(entry);
1572 } else {
1573 LOG(("nsOfflineCacheDevice::DeactivateEntry "
1574 "skipping update since entry is not dirty\n"));
1577 // Unlock the entry
1578 Unlock(*entry->Key());
1580 delete entry;
1582 return NS_OK;
1585 nsresult
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);
1611 if (!binding)
1612 return NS_ERROR_OUT_OF_MEMORY;
1613 binding->MarkNewEntry();
1615 nsOfflineCacheRecord rec;
1616 rec.clientID = cid;
1617 rec.key = key;
1618 rec.metaData = NULL; // don't write any metadata now.
1619 rec.metaDataLen = 0;
1620 rec.generation = binding->mGeneration;
1621 rec.dataSize = 0;
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)) {
1632 rv = tmp;
1634 tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
1635 if (NS_FAILED(tmp)) {
1636 rv = tmp;
1638 tmp = statement->BindInt32ByIndex(3, rec.generation);
1639 if (NS_FAILED(tmp)) {
1640 rv = tmp;
1642 tmp = statement->BindInt32ByIndex(4, rec.dataSize);
1643 if (NS_FAILED(tmp)) {
1644 rv = tmp;
1646 tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
1647 if (NS_FAILED(tmp)) {
1648 rv = tmp;
1650 tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
1651 if (NS_FAILED(tmp)) {
1652 rv = tmp;
1654 tmp = statement->BindInt64ByIndex(7, rec.lastModified);
1655 if (NS_FAILED(tmp)) {
1656 rv = tmp;
1658 tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
1659 if (NS_FAILED(tmp)) {
1660 rv = tmp;
1662 NS_ENSURE_SUCCESS(rv, rv);
1664 bool hasRows;
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);
1671 // lock the entry
1672 Lock(*entry->Key());
1674 return NS_OK;
1677 void
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
1688 // deactivated.
1690 DeleteEntry(entry, !entry->IsActive());
1693 nsresult
1694 nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
1695 nsCacheAccessMode mode,
1696 uint32_t offset,
1697 nsIInputStream **result)
1699 LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1700 entry->Key()->get()));
1702 *result = nullptr;
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);
1714 if (!in)
1715 return NS_ERROR_UNEXPECTED;
1717 // respect |offset| param
1718 if (offset != 0)
1720 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1721 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1723 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1726 in.swap(*result);
1727 return NS_OK;
1730 nsresult
1731 nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
1732 nsCacheAccessMode mode,
1733 uint32_t offset,
1734 nsIOutputStream **result)
1736 LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1737 entry->Key()->get()));
1739 *result = nullptr;
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,
1752 00600);
1753 if (!out)
1754 return NS_ERROR_UNEXPECTED;
1756 // respect |offset| param
1757 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1758 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1759 if (offset != 0)
1760 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1762 // truncate the file at the given offset
1763 seekable->SetEOF();
1765 nsCOMPtr<nsIOutputStream> bufferedOut;
1766 nsresult rv =
1767 NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
1768 NS_ENSURE_SUCCESS(rv, rv);
1770 bufferedOut.swap(*result);
1771 return NS_OK;
1774 nsresult
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);
1784 return NS_OK;
1787 nsresult
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
1796 // specified entry.
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
1809 // and abort
1810 #ifdef DEBUG
1811 nsresult rv =
1812 #endif
1813 nsCacheService::DoomEntry(entry);
1814 NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1815 return NS_ERROR_ABORT;
1818 mDeltaCounter = 0; // reset counter
1821 return NS_OK;
1824 nsresult
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);
1834 bool keepGoing;
1835 nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
1836 &keepGoing);
1837 if (NS_FAILED(rv))
1838 return rv;
1840 if (!keepGoing)
1841 return NS_OK;
1843 // SELECT * from moz_cache;
1845 nsOfflineCacheRecord rec;
1846 nsRefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1847 if (!info)
1848 return NS_ERROR_OUT_OF_MEMORY;
1849 info->mRec = &rec;
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);
1858 bool hasRows;
1859 for (;;)
1861 rv = statement->ExecuteStep(&hasRows);
1862 if (NS_FAILED(rv) || !hasRows)
1863 break;
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);
1876 bool keepGoing;
1877 rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1878 if (NS_FAILED(rv) || !keepGoing)
1879 break;
1882 info->mRec = nullptr;
1883 return NS_OK;
1886 nsresult
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;
1899 nsresult rv;
1900 if (clientID)
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);
1922 else
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.
1943 if (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);
1952 else
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);
1962 return NS_OK;
1965 nsresult
1966 nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
1967 const nsACString &key,
1968 uint32_t typeBits)
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);
1984 return NS_OK;
1987 nsresult
1988 nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
1989 const nsACString &key,
1990 uint32_t typeBits)
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();
2021 return NS_OK;
2024 nsresult
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()));
2032 nsresult rv;
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);
2041 bool hasRows;
2042 rv = statement->ExecuteStep(&hasRows);
2043 NS_ENSURE_SUCCESS(rv, rv);
2045 *out = nullptr;
2047 bool found = false;
2048 nsCString nsSpec;
2049 int32_t nsType = 0;
2050 nsCString nsData;
2052 while (hasRows)
2054 int32_t itemType;
2055 rv = statement->GetInt32(2, &itemType);
2056 NS_ENSURE_SUCCESS(rv, rv);
2058 if (!found || itemType > nsType)
2060 nsType = itemType;
2062 rv = statement->GetUTF8String(0, nsSpec);
2063 NS_ENSURE_SUCCESS(rv, rv);
2065 rv = statement->GetUTF8String(1, nsData);
2066 NS_ENSURE_SUCCESS(rv, rv);
2068 found = true;
2071 rv = statement->ExecuteStep(&hasRows);
2072 NS_ENSURE_SUCCESS(rv, rv);
2075 if (found) {
2076 nsCOMPtr<nsIApplicationCacheNamespace> ns =
2077 new nsApplicationCacheNamespace();
2078 if (!ns)
2079 return NS_ERROR_OUT_OF_MEMORY;
2080 rv = ns->Init(nsType, nsSpec, nsData);
2081 NS_ENSURE_SUCCESS(rv, rv);
2083 ns.swap(*out);
2086 return NS_OK;
2089 nsresult
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);
2099 nsresult
2100 nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
2101 const nsACString &key,
2102 uint32_t *typeBits)
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);
2113 bool hasRows;
2114 rv = statement->ExecuteStep(&hasRows);
2115 NS_ENSURE_SUCCESS(rv, rv);
2117 if (!hasRows)
2118 return NS_ERROR_CACHE_KEY_NOT_FOUND;
2120 *typeBits = statement->AsInt32(0);
2122 return NS_OK;
2125 nsresult
2126 nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
2127 uint32_t typeBits,
2128 uint32_t *count,
2129 char ***keys)
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);
2144 nsresult
2145 nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
2146 nsIApplicationCacheNamespace *ns)
2148 nsCString namespaceSpec;
2149 nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
2150 NS_ENSURE_SUCCESS(rv, rv);
2152 nsCString data;
2153 rv = ns->GetData(data);
2154 NS_ENSURE_SUCCESS(rv, rv);
2156 uint32_t itemType;
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);
2180 return NS_OK;
2183 nsresult
2184 nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
2185 uint32_t *usage)
2187 LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2188 PromiseFlatCString(clientID).get()));
2190 *usage = 0;
2192 AutoResetStatement statement(mStatement_ApplicationCacheSize);
2194 nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2195 NS_ENSURE_SUCCESS(rv, rv);
2197 bool hasRows;
2198 rv = statement->ExecuteStep(&hasRows);
2199 NS_ENSURE_SUCCESS(rv, rv);
2201 if (!hasRows)
2202 return NS_OK;
2204 *usage = static_cast<uint32_t>(statement->AsInt32(0));
2206 return NS_OK;
2209 nsresult
2210 nsOfflineCacheDevice::GetGroups(uint32_t *count,
2211 char ***keys)
2213 LOG(("nsOfflineCacheDevice::GetGroups"));
2215 return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
2218 nsresult
2219 nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
2220 char ***keys)
2222 LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
2224 return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
2227 bool
2228 nsOfflineCacheDevice::IsLocked(const nsACString &key)
2230 return mLockedEntries.GetEntry(key);
2233 void
2234 nsOfflineCacheDevice::Lock(const nsACString &key)
2236 mLockedEntries.PutEntry(key);
2239 void
2240 nsOfflineCacheDevice::Unlock(const nsACString &key)
2242 mLockedEntries.RemoveEntry(key);
2245 nsresult
2246 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
2247 uint32_t resultIndex,
2248 uint32_t * count,
2249 char *** values)
2251 bool hasRows;
2252 nsresult rv = statement->ExecuteStep(&hasRows);
2253 NS_ENSURE_SUCCESS(rv, rv);
2255 nsTArray<nsCString> valArray;
2256 while (hasRows)
2258 uint32_t length;
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());
2272 if (!ret[i]) {
2273 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
2274 return NS_ERROR_OUT_OF_MEMORY;
2278 *values = ret;
2280 return NS_OK;
2283 nsresult
2284 nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
2285 nsIApplicationCache **out)
2287 *out = nullptr;
2289 nsCString clientID;
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,
2305 group,
2306 clientID);
2307 if (!cache)
2308 return NS_ERROR_OUT_OF_MEMORY;
2310 nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
2311 if (!weak)
2312 return NS_ERROR_OUT_OF_MEMORY;
2314 mCaches.Put(clientID, weak);
2316 cache.swap(*out);
2318 return NS_OK;
2321 nsresult
2322 nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
2323 nsIApplicationCache **out)
2325 *out = nullptr;
2327 nsCOMPtr<nsIApplicationCache> cache;
2329 nsWeakPtr weak;
2330 if (mCaches.Get(clientID, getter_AddRefs(weak)))
2331 cache = do_QueryReferent(weak);
2333 if (!cache)
2335 nsCString group;
2336 nsresult rv = GetGroupForCache(clientID, group);
2337 NS_ENSURE_SUCCESS(rv, rv);
2339 if (group.IsEmpty()) {
2340 return NS_OK;
2343 cache = new nsApplicationCache(this, group, clientID);
2344 weak = do_GetWeakReference(cache);
2345 if (!weak)
2346 return NS_ERROR_OUT_OF_MEMORY;
2348 mCaches.Put(clientID, weak);
2351 cache.swap(*out);
2353 return NS_OK;
2356 nsresult
2357 nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
2358 nsIApplicationCache **out)
2360 *out = nullptr;
2362 nsCString *clientID;
2363 if (mActiveCachesByGroup.Get(group, &clientID))
2364 return GetApplicationCache(*clientID, out);
2366 return NS_OK;
2369 nsresult
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);
2385 active = nullptr;
2388 return NS_OK;
2391 nsresult
2392 nsOfflineCacheDevice::DiscardByAppId(int32_t appID, bool browserEntriesOnly)
2394 nsresult rv;
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);
2405 bool hasRows;
2406 rv = statement->ExecuteStep(&hasRows);
2407 NS_ENSURE_SUCCESS(rv, rv);
2409 while (hasRows) {
2410 nsAutoCString group;
2411 rv = statement->GetUTF8String(0, group);
2412 NS_ENSURE_SUCCESS(rv, rv);
2414 nsCString clientID;
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);
2434 return NS_OK;
2437 bool
2438 nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
2439 const nsACString &clientID,
2440 nsILoadContext *loadContext)
2442 if (!mActiveCaches.Contains(clientID))
2443 return false;
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);
2451 if (NS_FAILED(rv))
2452 return false;
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
2459 // manifest origin.
2460 if (!NS_SecurityCompareURIs(keyURI, groupURI,
2461 GetStrictFileOriginPolicy()))
2462 return false;
2464 // Get extended origin attributes
2465 uint32_t appId = NECKO_NO_APP_ID;
2466 bool isInBrowserElement = false;
2468 if (loadContext) {
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,
2481 demandedGroupID);
2482 NS_ENSURE_SUCCESS(rv, false);
2484 if (groupID != demandedGroupID)
2485 return false;
2487 return true;
2491 nsresult
2492 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
2493 nsILoadContext *loadContext,
2494 nsIApplicationCache **out)
2496 *out = nullptr;
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);
2507 bool hasRows;
2508 rv = statement->ExecuteStep(&hasRows);
2509 NS_ENSURE_SUCCESS(rv, rv);
2511 while (hasRows) {
2512 int32_t itemType;
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);
2541 while (hasRows)
2543 int32_t itemType;
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);
2562 return NS_OK;
2565 nsresult
2566 nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
2567 const nsACString &key)
2569 NS_ENSURE_ARG_POINTER(cache);
2571 nsresult rv;
2573 nsAutoCString clientID;
2574 rv = cache->GetClientID(clientID);
2575 NS_ENSURE_SUCCESS(rv, rv);
2577 return CacheOpportunistically(clientID, key);
2580 nsresult
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);
2595 nsCString *active;
2596 if (mActiveCachesByGroup.Get(group, &active))
2598 mActiveCaches.RemoveEntry(*active);
2599 mActiveCachesByGroup.Remove(group);
2600 active = nullptr;
2603 if (!clientID.IsEmpty())
2605 mActiveCaches.PutEntry(clientID);
2606 mActiveCachesByGroup.Put(group, new nsCString(clientID));
2609 return NS_OK;
2612 bool
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
2624 void
2625 nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
2627 if (Initialized())
2629 NS_ERROR("cannot switch cache directory once initialized");
2630 return;
2633 if (!parentDir)
2635 mCacheDirectory = nullptr;
2636 return;
2639 // ensure parent directory exists
2640 nsresult rv = EnsureDir(parentDir);
2641 if (NS_FAILED(rv))
2643 NS_WARNING("unable to create parent directory");
2644 return;
2647 mBaseDirectory = parentDir;
2649 // cache dir may not exist, but that's ok
2650 nsCOMPtr<nsIFile> dir;
2651 rv = parentDir->Clone(getter_AddRefs(dir));
2652 if (NS_FAILED(rv))
2653 return;
2654 rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2655 if (NS_FAILED(rv))
2656 return;
2658 mCacheDirectory = do_QueryInterface(dir);
2661 void
2662 nsOfflineCacheDevice::SetCapacity(uint32_t capacity)
2664 mCacheCapacity = capacity * 1024;
2667 bool
2668 nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache)
2670 if (!mAutoShutdown)
2671 return false;
2673 mAutoShutdown = false;
2675 Shutdown();
2677 nsRefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
2678 cacheService->RemoveCustomOfflineDevice(this);
2680 nsAutoCString clientID;
2681 aAppCache->GetClientID(clientID);
2682 mCaches.Remove(clientID);
2684 return true;