michael@0: /* -*- Mode: C++; indent-tab-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "nsCache.h" michael@0: #include "nsDiskCache.h" michael@0: #include "nsDiskCacheDeviceSQL.h" michael@0: #include "nsCacheService.h" michael@0: #include "nsApplicationCache.h" michael@0: michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsString.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsCRT.h" michael@0: #include "nsArrayUtils.h" michael@0: #include "nsIArray.h" michael@0: #include "nsIVariant.h" michael@0: #include "nsILoadContextInfo.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsISerializable.h" michael@0: #include "nsSerializationHelper.h" michael@0: michael@0: #include "mozIStorageService.h" michael@0: #include "mozIStorageStatement.h" michael@0: #include "mozIStorageFunction.h" michael@0: #include "mozStorageHelper.h" michael@0: michael@0: #include "nsICacheVisitor.h" michael@0: #include "nsISeekableStream.h" michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "sqlite3.h" michael@0: #include "mozilla/storage.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::storage; michael@0: michael@0: static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" }; michael@0: michael@0: #define LOG(args) CACHE_LOG_DEBUG(args) michael@0: michael@0: static uint32_t gNextTemporaryClientID = 0; michael@0: michael@0: /***************************************************************************** michael@0: * helpers michael@0: */ michael@0: michael@0: static nsresult michael@0: EnsureDir(nsIFile *dir) michael@0: { michael@0: bool exists; michael@0: nsresult rv = dir->Exists(&exists); michael@0: if (NS_SUCCEEDED(rv) && !exists) michael@0: rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); michael@0: return rv; michael@0: } michael@0: michael@0: static bool michael@0: DecomposeCacheEntryKey(const nsCString *fullKey, michael@0: const char **cid, michael@0: const char **key, michael@0: nsCString &buf) michael@0: { michael@0: buf = *fullKey; michael@0: michael@0: int32_t colon = buf.FindChar(':'); michael@0: if (colon == kNotFound) michael@0: { michael@0: NS_ERROR("Invalid key"); michael@0: return false; michael@0: } michael@0: buf.SetCharAt('\0', colon); michael@0: michael@0: *cid = buf.get(); michael@0: *key = buf.get() + colon + 1; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: class AutoResetStatement michael@0: { michael@0: public: michael@0: AutoResetStatement(mozIStorageStatement *s) michael@0: : mStatement(s) {} michael@0: ~AutoResetStatement() { mStatement->Reset(); } michael@0: mozIStorageStatement *operator->() { return mStatement; } michael@0: private: michael@0: mozIStorageStatement *mStatement; michael@0: }; michael@0: michael@0: class EvictionObserver michael@0: { michael@0: public: michael@0: EvictionObserver(mozIStorageConnection *db, michael@0: nsOfflineCacheEvictionFunction *evictionFunction) michael@0: : mDB(db), mEvictionFunction(evictionFunction) michael@0: { michael@0: mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE" michael@0: " ON moz_cache FOR EACH ROW BEGIN SELECT" michael@0: " cache_eviction_observer(" michael@0: " OLD.ClientID, OLD.key, OLD.generation);" michael@0: " END;")); michael@0: mEvictionFunction->Reset(); michael@0: } michael@0: michael@0: ~EvictionObserver() michael@0: { michael@0: mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;")); michael@0: mEvictionFunction->Reset(); michael@0: } michael@0: michael@0: void Apply() { return mEvictionFunction->Apply(); } michael@0: michael@0: private: michael@0: mozIStorageConnection *mDB; michael@0: nsRefPtr mEvictionFunction; michael@0: }; michael@0: michael@0: #define DCACHE_HASH_MAX INT64_MAX michael@0: #define DCACHE_HASH_BITS 64 michael@0: michael@0: /** michael@0: * nsOfflineCache::Hash(const char * key) michael@0: * michael@0: * This algorithm of this method implies nsOfflineCacheRecords will be stored michael@0: * in a certain order on disk. If the algorithm changes, existing cache michael@0: * map files may become invalid, and therefore the kCurrentVersion needs michael@0: * to be revised. michael@0: */ michael@0: static uint64_t michael@0: DCacheHash(const char * key) michael@0: { michael@0: // initval 0x7416f295 was chosen randomly michael@0: return (uint64_t(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295); michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheEvictionFunction michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction) michael@0: michael@0: // helper function for directly exposing the same data file binding michael@0: // path algorithm used in nsOfflineCacheBinding::Create michael@0: static nsresult michael@0: GetCacheDataFile(nsIFile *cacheDir, const char *key, michael@0: int generation, nsCOMPtr &file) michael@0: { michael@0: cacheDir->Clone(getter_AddRefs(file)); michael@0: if (!file) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint64_t hash = DCacheHash(key); michael@0: michael@0: uint32_t dir1 = (uint32_t) (hash & 0x0F); michael@0: uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4); michael@0: michael@0: hash >>= 8; michael@0: michael@0: file->AppendNative(nsPrintfCString("%X", dir1)); michael@0: file->AppendNative(nsPrintfCString("%X", dir2)); michael@0: michael@0: char leaf[64]; michael@0: PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation); michael@0: return file->AppendNative(nsDependentCString(leaf)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval) michael@0: { michael@0: LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n")); michael@0: michael@0: *_retval = nullptr; michael@0: michael@0: uint32_t numEntries; michael@0: nsresult rv = values->GetNumEntries(&numEntries); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(numEntries == 3, "unexpected number of arguments"); michael@0: michael@0: uint32_t valueLen; michael@0: const char *clientID = values->AsSharedUTF8String(0, &valueLen); michael@0: const char *key = values->AsSharedUTF8String(1, &valueLen); michael@0: nsAutoCString fullKey(clientID); michael@0: fullKey.AppendLiteral(":"); michael@0: fullKey.Append(key); michael@0: int generation = values->AsInt32(2); michael@0: michael@0: // If the key is currently locked, refuse to delete this row. michael@0: if (mDevice->IsLocked(fullKey)) { michael@0: NS_ADDREF(*_retval = new IntegerVariant(SQLITE_IGNORE)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = GetCacheDataFile(mDevice->CacheDirectory(), key, michael@0: generation, file); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n", michael@0: key, generation, rv)); michael@0: return rv; michael@0: } michael@0: michael@0: mItems.AppendObject(file); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheEvictionFunction::Apply() michael@0: { michael@0: LOG(("nsOfflineCacheEvictionFunction::Apply\n")); michael@0: michael@0: for (int32_t i = 0; i < mItems.Count(); i++) { michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString path; michael@0: mItems[i]->GetNativePath(path); michael@0: LOG((" removing %s\n", path.get())); michael@0: #endif michael@0: michael@0: mItems[i]->Remove(false); michael@0: } michael@0: michael@0: Reset(); michael@0: } michael@0: michael@0: class nsOfflineCacheDiscardCache : public nsRunnable michael@0: { michael@0: public: michael@0: nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device, michael@0: nsCString &group, michael@0: nsCString &clientID) michael@0: : mDevice(device) michael@0: , mGroup(group) michael@0: , mClientID(clientID) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: if (mDevice->IsActiveCache(mGroup, mClientID)) michael@0: { michael@0: mDevice->DeactivateGroup(mGroup); michael@0: } michael@0: michael@0: return mDevice->EvictEntries(mClientID.get()); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mDevice; michael@0: nsCString mGroup; michael@0: nsCString mClientID; michael@0: }; michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheDeviceInfo michael@0: */ michael@0: michael@0: class nsOfflineCacheDeviceInfo MOZ_FINAL : public nsICacheDeviceInfo michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSICACHEDEVICEINFO michael@0: michael@0: nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device) michael@0: : mDevice(device) michael@0: {} michael@0: michael@0: private: michael@0: nsOfflineCacheDevice* mDevice; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo) michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheDeviceInfo::GetDescription(char **aDescription) michael@0: { michael@0: *aDescription = NS_strdup("Offline cache device"); michael@0: return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport) michael@0: { michael@0: nsAutoCString buffer; michael@0: buffer.AssignLiteral(" \n" michael@0: " Cache Directory:\n" michael@0: " "); michael@0: nsIFile *cacheDir = mDevice->CacheDirectory(); michael@0: if (!cacheDir) michael@0: return NS_OK; michael@0: michael@0: nsAutoString path; michael@0: nsresult rv = cacheDir->GetPath(path); michael@0: if (NS_SUCCEEDED(rv)) michael@0: AppendUTF16toUTF8(path, buffer); michael@0: else michael@0: buffer.AppendLiteral("directory unavailable"); michael@0: michael@0: buffer.AppendLiteral("\n" michael@0: " \n"); michael@0: michael@0: *usageReport = ToNewCString(buffer); michael@0: if (!*usageReport) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount) michael@0: { michael@0: *aEntryCount = mDevice->EntryCount(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize) michael@0: { michael@0: *aTotalSize = mDevice->CacheSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize) michael@0: { michael@0: *aMaximumSize = mDevice->CacheCapacity(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheBinding michael@0: */ michael@0: michael@0: class nsOfflineCacheBinding MOZ_FINAL : public nsISupports michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: static nsOfflineCacheBinding * michael@0: Create(nsIFile *cacheDir, const nsCString *key, int generation); michael@0: michael@0: enum { FLAG_NEW_ENTRY = 1 }; michael@0: michael@0: nsCOMPtr mDataFile; michael@0: int mGeneration; michael@0: int mFlags; michael@0: michael@0: bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; } michael@0: void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; } michael@0: void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding) michael@0: michael@0: nsOfflineCacheBinding * michael@0: nsOfflineCacheBinding::Create(nsIFile *cacheDir, michael@0: const nsCString *fullKey, michael@0: int generation) michael@0: { michael@0: nsCOMPtr file; michael@0: cacheDir->Clone(getter_AddRefs(file)); michael@0: if (!file) michael@0: return nullptr; michael@0: michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) michael@0: return nullptr; michael@0: michael@0: uint64_t hash = DCacheHash(key); michael@0: michael@0: uint32_t dir1 = (uint32_t) (hash & 0x0F); michael@0: uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4); michael@0: michael@0: hash >>= 8; michael@0: michael@0: // XXX we might want to create these directories up-front michael@0: michael@0: file->AppendNative(nsPrintfCString("%X", dir1)); michael@0: file->Create(nsIFile::DIRECTORY_TYPE, 00700); michael@0: michael@0: file->AppendNative(nsPrintfCString("%X", dir2)); michael@0: file->Create(nsIFile::DIRECTORY_TYPE, 00700); michael@0: michael@0: nsresult rv; michael@0: char leaf[64]; michael@0: michael@0: if (generation == -1) michael@0: { michael@0: file->AppendNative(NS_LITERAL_CSTRING("placeholder")); michael@0: michael@0: for (generation = 0; ; ++generation) michael@0: { michael@0: PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation); michael@0: michael@0: rv = file->SetNativeLeafName(nsDependentCString(leaf)); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) michael@0: return nullptr; michael@0: if (NS_SUCCEEDED(rv)) michael@0: break; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation); michael@0: rv = file->AppendNative(nsDependentCString(leaf)); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: } michael@0: michael@0: nsOfflineCacheBinding *binding = new nsOfflineCacheBinding; michael@0: if (!binding) michael@0: return nullptr; michael@0: michael@0: binding->mDataFile.swap(file); michael@0: binding->mGeneration = generation; michael@0: binding->mFlags = 0; michael@0: return binding; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheRecord michael@0: */ michael@0: michael@0: struct nsOfflineCacheRecord michael@0: { michael@0: const char *clientID; michael@0: const char *key; michael@0: const uint8_t *metaData; michael@0: uint32_t metaDataLen; michael@0: int32_t generation; michael@0: int32_t dataSize; michael@0: int32_t fetchCount; michael@0: int64_t lastFetched; michael@0: int64_t lastModified; michael@0: int64_t expirationTime; michael@0: }; michael@0: michael@0: static nsCacheEntry * michael@0: CreateCacheEntry(nsOfflineCacheDevice *device, michael@0: const nsCString *fullKey, michael@0: const nsOfflineCacheRecord &rec) michael@0: { michael@0: nsCacheEntry *entry; michael@0: michael@0: if (device->IsLocked(*fullKey)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing michael@0: nsICache::STREAM_BASED, michael@0: nsICache::STORE_OFFLINE, michael@0: device, &entry); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: michael@0: entry->SetFetchCount((uint32_t) rec.fetchCount); michael@0: entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched)); michael@0: entry->SetLastModified(SecondsFromPRTime(rec.lastModified)); michael@0: entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime)); michael@0: entry->SetDataSize((uint32_t) rec.dataSize); michael@0: michael@0: entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen); michael@0: michael@0: // Restore security info, if present michael@0: const char* info = entry->GetMetaDataElement("security-info"); michael@0: if (info) { michael@0: nsCOMPtr infoObj; michael@0: rv = NS_DeserializeObject(nsDependentCString(info), michael@0: getter_AddRefs(infoObj)); michael@0: if (NS_FAILED(rv)) { michael@0: delete entry; michael@0: return nullptr; michael@0: } michael@0: entry->SetSecurityInfo(infoObj); michael@0: } michael@0: michael@0: // create a binding object for this entry michael@0: nsOfflineCacheBinding *binding = michael@0: nsOfflineCacheBinding::Create(device->CacheDirectory(), michael@0: fullKey, michael@0: rec.generation); michael@0: if (!binding) michael@0: { michael@0: delete entry; michael@0: return nullptr; michael@0: } michael@0: entry->SetData(binding); michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheEntryInfo michael@0: */ michael@0: michael@0: class nsOfflineCacheEntryInfo MOZ_FINAL : public nsICacheEntryInfo michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSICACHEENTRYINFO michael@0: michael@0: nsOfflineCacheRecord *mRec; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo) michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetClientID(char **result) michael@0: { michael@0: *result = NS_strdup(mRec->clientID); michael@0: return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID) michael@0: { michael@0: *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID); michael@0: return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey) michael@0: { michael@0: clientKey.Assign(mRec->key); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount) michael@0: { michael@0: *aFetchCount = mRec->fetchCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched) michael@0: { michael@0: *aLastFetched = SecondsFromPRTime(mRec->lastFetched); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified) michael@0: { michael@0: *aLastModified = SecondsFromPRTime(mRec->lastModified); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime) michael@0: { michael@0: *aExpirationTime = SecondsFromPRTime(mRec->expirationTime); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased) michael@0: { michael@0: *aStreamBased = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize) michael@0: { michael@0: *aDataSize = mRec->dataSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsApplicationCacheNamespace michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace) michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCacheNamespace::Init(uint32_t itemType, michael@0: const nsACString &namespaceSpec, michael@0: const nsACString &data) michael@0: { michael@0: mItemType = itemType; michael@0: mNamespaceSpec = namespaceSpec; michael@0: mData = data; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCacheNamespace::GetItemType(uint32_t *out) michael@0: { michael@0: *out = mItemType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out) michael@0: { michael@0: out = mNamespaceSpec; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCacheNamespace::GetData(nsACString &out) michael@0: { michael@0: out = mData; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsApplicationCache michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsApplicationCache, michael@0: nsIApplicationCache, michael@0: nsISupportsWeakReference) michael@0: michael@0: nsApplicationCache::nsApplicationCache() michael@0: : mDevice(nullptr) michael@0: , mValid(true) michael@0: { michael@0: } michael@0: michael@0: nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device, michael@0: const nsACString &group, michael@0: const nsACString &clientID) michael@0: : mDevice(device) michael@0: , mGroup(group) michael@0: , mClientID(clientID) michael@0: , mValid(true) michael@0: { michael@0: } michael@0: michael@0: nsApplicationCache::~nsApplicationCache() michael@0: { michael@0: if (!mDevice) michael@0: return; michael@0: michael@0: { michael@0: MutexAutoLock lock(mDevice->mLock); michael@0: mDevice->mCaches.Remove(mClientID); michael@0: } michael@0: michael@0: // If this isn't an active cache anymore, it can be destroyed. michael@0: if (mValid && !mDevice->IsActiveCache(mGroup, mClientID)) michael@0: Discard(); michael@0: } michael@0: michael@0: void michael@0: nsApplicationCache::MarkInvalid() michael@0: { michael@0: mValid = false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::InitAsHandle(const nsACString &groupId, michael@0: const nsACString &clientId) michael@0: { michael@0: NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED); michael@0: NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: mGroup = groupId; michael@0: mClientID = clientId; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetManifestURI(nsIURI **out) michael@0: { michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = uri->CloneIgnoringRef(out); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetGroupID(nsACString &out) michael@0: { michael@0: out = mGroup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetClientID(nsACString &out) michael@0: { michael@0: out = mClientID; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetProfileDirectory(nsIFile **out) michael@0: { michael@0: if (mDevice->BaseDirectory()) michael@0: NS_ADDREF(*out = mDevice->BaseDirectory()); michael@0: else michael@0: *out = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetActive(bool *out) michael@0: { michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: *out = mDevice->IsActiveCache(mGroup, mClientID); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::Activate() michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: mDevice->ActivateCache(mGroup, mClientID); michael@0: michael@0: if (mDevice->AutoShutdown(this)) michael@0: mDevice = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::Discard() michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: mValid = false; michael@0: michael@0: nsRefPtr ev = michael@0: new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID); michael@0: nsresult rv = nsCacheService::DispatchToCacheIOThread(ev); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::MarkEntry(const nsACString &key, michael@0: uint32_t typeBits) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->MarkEntry(mClientID, key, typeBits); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::UnmarkEntry(const nsACString &key, michael@0: uint32_t typeBits) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->UnmarkEntry(mClientID, key, typeBits); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetTypes(const nsACString &key, michael@0: uint32_t *typeBits) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->GetTypes(mClientID, key, typeBits); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GatherEntries(uint32_t typeBits, michael@0: uint32_t * count, michael@0: char *** keys) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->GatherEntries(mClientID, typeBits, count, keys); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::AddNamespaces(nsIArray *namespaces) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: if (!namespaces) michael@0: return NS_OK; michael@0: michael@0: mozStorageTransaction transaction(mDevice->mDB, false); michael@0: michael@0: uint32_t length; michael@0: nsresult rv = namespaces->GetLength(&length); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: nsCOMPtr ns = michael@0: do_QueryElementAt(namespaces, i); michael@0: if (ns) { michael@0: rv = mDevice->AddNamespace(mClientID, ns); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: rv = transaction.Commit(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetMatchingNamespace(const nsACString &key, michael@0: nsIApplicationCacheNamespace **out) michael@0: michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->GetMatchingNamespace(mClientID, key, out); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsApplicationCache::GetUsage(uint32_t *usage) michael@0: { michael@0: NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return mDevice->GetUsage(mClientID, usage); michael@0: } michael@0: michael@0: /****************************************************************************** michael@0: * nsCloseDBEvent michael@0: *****************************************************************************/ michael@0: michael@0: class nsCloseDBEvent : public nsRunnable { michael@0: public: michael@0: nsCloseDBEvent(mozIStorageConnection *aDB) michael@0: { michael@0: mDB = aDB; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mDB->Close(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: virtual ~nsCloseDBEvent() {} michael@0: michael@0: private: michael@0: nsCOMPtr mDB; michael@0: }; michael@0: michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsOfflineCacheDevice michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice) michael@0: michael@0: nsOfflineCacheDevice::nsOfflineCacheDevice() michael@0: : mDB(nullptr) michael@0: , mCacheCapacity(0) michael@0: , mDeltaCounter(0) michael@0: , mAutoShutdown(false) michael@0: , mLock("nsOfflineCacheDevice.lock") michael@0: , mActiveCaches(5) michael@0: , mLockedEntries(64) michael@0: { michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: nsOfflineCacheDevice::GetStrictFileOriginPolicy() michael@0: { michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: michael@0: bool retval; michael@0: if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval))) michael@0: return retval; michael@0: michael@0: // As default value use true (be more strict) michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: nsOfflineCacheDevice::CacheSize() michael@0: { michael@0: AutoResetStatement statement(mStatement_CacheSize); michael@0: michael@0: bool hasRows; michael@0: nsresult rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0); michael@0: michael@0: return (uint32_t) statement->AsInt32(0); michael@0: } michael@0: michael@0: uint32_t michael@0: nsOfflineCacheDevice::EntryCount() michael@0: { michael@0: AutoResetStatement statement(mStatement_EntryCount); michael@0: michael@0: bool hasRows; michael@0: nsresult rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0); michael@0: michael@0: return (uint32_t) statement->AsInt32(0); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry) michael@0: { michael@0: // Decompose the key into "ClientID" and "Key" michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: michael@0: if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Store security info, if it is serializable michael@0: nsCOMPtr infoObj = entry->SecurityInfo(); michael@0: nsCOMPtr serializable = do_QueryInterface(infoObj); michael@0: if (infoObj && !serializable) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: if (serializable) { michael@0: nsCString info; michael@0: nsresult rv = NS_SerializeToString(serializable, info); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = entry->SetMetaDataElement("security-info", info.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCString metaDataBuf; michael@0: uint32_t mdSize = entry->MetaDataSize(); michael@0: if (!metaDataBuf.SetLength(mdSize, fallible_t())) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: char *md = metaDataBuf.BeginWriting(); michael@0: entry->FlattenMetaData(md, mdSize); michael@0: michael@0: nsOfflineCacheRecord rec; michael@0: rec.metaData = (const uint8_t *) md; michael@0: rec.metaDataLen = mdSize; michael@0: rec.dataSize = entry->DataSize(); michael@0: rec.fetchCount = entry->FetchCount(); michael@0: rec.lastFetched = PRTimeFromSeconds(entry->LastFetched()); michael@0: rec.lastModified = PRTimeFromSeconds(entry->LastModified()); michael@0: rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime()); michael@0: michael@0: AutoResetStatement statement(mStatement_UpdateEntry); michael@0: michael@0: nsresult rv; michael@0: rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen); michael@0: nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt32ByIndex(2, rec.fetchCount); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(3, rec.lastFetched); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(4, rec.lastModified); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(5, rec.expirationTime); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid)); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key)); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(!hasRows, "UPDATE should not result in output"); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize) michael@0: { michael@0: // Decompose the key into "ClientID" and "Key" michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: AutoResetStatement statement(mStatement_UpdateEntrySize); michael@0: michael@0: nsresult rv = statement->BindInt32ByIndex(0, newSize); michael@0: nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid)); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key)); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(!hasRows, "UPDATE should not result in output"); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData) michael@0: { michael@0: if (deleteData) michael@0: { michael@0: nsresult rv = DeleteData(entry); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: // Decompose the key into "ClientID" and "Key" michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: AutoResetStatement statement(mStatement_DeleteEntry); michael@0: michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid)); michael@0: nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_SUCCESS(rv2, rv2); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(!hasRows, "DELETE should not result in output"); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry) michael@0: { michael@0: nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data(); michael@0: NS_ENSURE_STATE(binding); michael@0: michael@0: return binding->mDataFile->Remove(false); michael@0: } michael@0: michael@0: /** michael@0: * nsCacheDevice implementation michael@0: */ michael@0: michael@0: // This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't michael@0: // allow a template (mozilla::ArrayLength) to be instantiated based on a local michael@0: // type. Boo-urns! michael@0: struct StatementSql { michael@0: nsCOMPtr &statement; michael@0: const char *sql; michael@0: StatementSql (nsCOMPtr &aStatement, const char *aSql): michael@0: statement (aStatement), sql (aSql) {} michael@0: }; michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::Init() michael@0: { michael@0: MOZ_ASSERT(false, "Need to be initialized with sqlite"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::InitWithSqlite(mozIStorageService * ss) michael@0: { michael@0: NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: // SetCacheParentDirectory must have been called michael@0: NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED); michael@0: michael@0: // make sure the cache directory exists michael@0: nsresult rv = EnsureDir(mCacheDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // build path to index file michael@0: nsCOMPtr indexFile; michael@0: rv = mCacheDirectory->Clone(getter_AddRefs(indexFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(ss, "nsOfflineCacheDevice::InitWithSqlite called before nsCacheService::Init() ?"); michael@0: NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED); michael@0: michael@0: rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mInitThread = do_GetCurrentThread(); michael@0: michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;")); michael@0: michael@0: // XXX ... other initialization steps michael@0: michael@0: // XXX in the future we may wish to verify the schema for moz_cache michael@0: // perhaps using "PRAGMA table_info" ? michael@0: michael@0: // build the table michael@0: // michael@0: // "Generation" is the data file generation number. michael@0: // michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n" michael@0: " ClientID TEXT,\n" michael@0: " Key TEXT,\n" michael@0: " MetaData BLOB,\n" michael@0: " Generation INTEGER,\n" michael@0: " DataSize INTEGER,\n" michael@0: " FetchCount INTEGER,\n" michael@0: " LastFetched INTEGER,\n" michael@0: " LastModified INTEGER,\n" michael@0: " ExpirationTime INTEGER,\n" michael@0: " ItemType INTEGER DEFAULT 0\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Databases from 1.9.0 don't have the ItemType column. Add the column michael@0: // here, but don't worry about failures (the column probably already exists) michael@0: mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0")); michael@0: michael@0: // Create the table for storing cache groups. All actions on michael@0: // moz_cache_groups use the GroupID, so use it as the primary key. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n" michael@0: " GroupID TEXT PRIMARY KEY,\n" michael@0: " ActiveClientID TEXT\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups " michael@0: "ADD ActivateTimeStamp INTEGER DEFAULT 0")); michael@0: michael@0: // ClientID: clientID joining moz_cache and moz_cache_namespaces michael@0: // tables. michael@0: // Data: Data associated with this namespace (e.g. a fallback URI michael@0: // for fallback entries). michael@0: // ItemType: the type of namespace. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS" michael@0: " moz_cache_namespaces (\n" michael@0: " ClientID TEXT,\n" michael@0: " NameSpace TEXT,\n" michael@0: " Data TEXT,\n" michael@0: " ItemType INTEGER\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Databases from 1.9.0 have a moz_cache_index that should be dropped michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Key/ClientID pairs should be unique in the database. All queries michael@0: // against moz_cache use the Key (which is also the most unique), so michael@0: // use it as the primary key for this index. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS " michael@0: " moz_cache_key_clientid_index" michael@0: " ON moz_cache (Key, ClientID);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS" michael@0: " moz_cache_namespaces_clientid_index" michael@0: " ON moz_cache_namespaces (ClientID, NameSpace);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Used for namespace lookups. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS" michael@0: " moz_cache_namespaces_namespace_index" michael@0: " ON moz_cache_namespaces (NameSpace);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: michael@0: mEvictionFunction = new nsOfflineCacheEvictionFunction(this); michael@0: if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // create all (most) of our statements up front michael@0: StatementSql prepared[] = { michael@0: StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ), michael@0: StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ), michael@0: StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ), michael@0: StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ), michael@0: michael@0: StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ), michael@0: StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"), michael@0: StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ), michael@0: StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ), michael@0: michael@0: StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ), michael@0: StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ), michael@0: StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ), michael@0: michael@0: // Search for namespaces that match the URI. Use the <= operator michael@0: // to ensure that we use the index on moz_cache_namespaces. michael@0: StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM" michael@0: " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups" michael@0: " ON ns.ClientID = groups.ActiveClientID" michael@0: " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'" michael@0: " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"), michael@0: StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces" michael@0: " WHERE ClientID = ?1" michael@0: " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'" michael@0: " ORDER BY NameSpace DESC;"), michael@0: StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"), michael@0: StatementSql ( mStatement_EnumerateApps, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"), michael@0: StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"), michael@0: StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;") michael@0: }; michael@0: for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i) michael@0: { michael@0: LOG(("Creating statement: %s\n", prepared[i].sql)); michael@0: michael@0: rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql), michael@0: getter_AddRefs(prepared[i].statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = InitActiveCaches(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: nsresult michael@0: GetGroupForCache(const nsCSubstring &clientID, nsCString &group) michael@0: { michael@0: group.Assign(clientID); michael@0: group.Truncate(group.FindChar('|')); michael@0: NS_UnescapeURL(group); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AppendJARIdentifier(nsACString &_result, int32_t appId, bool isInBrowserElement) michael@0: { michael@0: _result.Append('#'); michael@0: _result.AppendInt(appId); michael@0: _result.Append('+'); michael@0: _result.Append(isInBrowserElement ? 't' : 'f'); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: GetJARIdentifier(nsIURI *aURI, michael@0: uint32_t appId, bool isInBrowserElement, michael@0: nsACString &_result) michael@0: { michael@0: _result.Truncate(); michael@0: michael@0: // These lines are here for compatibility only. We must not fill the michael@0: // JAR identifier when this is no-app context, otherwise web content michael@0: // offline application cache loads would not be satisfied (cache would michael@0: // not be found). michael@0: if (!isInBrowserElement && appId == NECKO_NO_APP_ID) michael@0: return NS_OK; michael@0: michael@0: // This load context has some special attributes, create a jar identifier michael@0: return AppendJARIdentifier(_result, appId, isInBrowserElement); michael@0: } michael@0: michael@0: } // anon namespace michael@0: michael@0: // static michael@0: nsresult michael@0: nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL, michael@0: uint32_t appId, bool isInBrowserElement, michael@0: nsACString &_result) michael@0: { michael@0: nsCOMPtr newURI; michael@0: nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString manifestSpec; michael@0: rv = newURI->GetAsciiSpec(manifestSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: _result.Assign(manifestSpec); michael@0: michael@0: nsAutoCString jarid; michael@0: rv = GetJARIdentifier(aManifestURL, appId, isInBrowserElement, jarid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Include JAR ID, i.e. the extended origin if present. michael@0: if (!jarid.IsEmpty()) michael@0: _result.Append(jarid); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::InitActiveCaches() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: AutoResetStatement statement(mStatement_EnumerateGroups); michael@0: michael@0: bool hasRows; michael@0: nsresult rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (hasRows) michael@0: { michael@0: nsAutoCString group; michael@0: statement->GetUTF8String(0, group); michael@0: nsCString clientID; michael@0: statement->GetUTF8String(1, clientID); michael@0: michael@0: mActiveCaches.PutEntry(clientID); michael@0: mActiveCachesByGroup.Put(group, new nsCString(clientID)); michael@0: michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ michael@0: PLDHashOperator michael@0: nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key, michael@0: nsIWeakReference *weakRef, michael@0: void *ctx) michael@0: { michael@0: nsCOMPtr obj = do_QueryReferent(weakRef); michael@0: if (obj) michael@0: { michael@0: nsApplicationCache *appCache = static_cast(obj.get()); michael@0: appCache->MarkInvalid(); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::Shutdown() michael@0: { michael@0: NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mCaches.EnumerateRead(ShutdownApplicationCache, this); michael@0: } michael@0: michael@0: { michael@0: EvictionObserver evictionObserver(mDB, mEvictionFunction); michael@0: michael@0: // Delete all rows whose clientID is not an active clientID. michael@0: nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_cache WHERE rowid IN" michael@0: " (SELECT moz_cache.rowid FROM" michael@0: " moz_cache LEFT OUTER JOIN moz_cache_groups ON" michael@0: " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)" michael@0: " WHERE moz_cache_groups.GroupID ISNULL)")); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("Failed to clean up unused application caches."); michael@0: else michael@0: evictionObserver.Apply(); michael@0: michael@0: // Delete all namespaces whose clientID is not an active clientID. michael@0: rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_cache_namespaces WHERE rowid IN" michael@0: " (SELECT moz_cache_namespaces.rowid FROM" michael@0: " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON" michael@0: " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)" michael@0: " WHERE moz_cache_groups.GroupID ISNULL)")); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("Failed to clean up namespaces."); michael@0: michael@0: mEvictionFunction = 0; michael@0: michael@0: mStatement_CacheSize = nullptr; michael@0: mStatement_ApplicationCacheSize = nullptr; michael@0: mStatement_EntryCount = nullptr; michael@0: mStatement_UpdateEntry = nullptr; michael@0: mStatement_UpdateEntrySize = nullptr; michael@0: mStatement_DeleteEntry = nullptr; michael@0: mStatement_FindEntry = nullptr; michael@0: mStatement_BindEntry = nullptr; michael@0: mStatement_ClearDomain = nullptr; michael@0: mStatement_MarkEntry = nullptr; michael@0: mStatement_UnmarkEntry = nullptr; michael@0: mStatement_GetTypes = nullptr; michael@0: mStatement_FindNamespaceEntry = nullptr; michael@0: mStatement_InsertNamespaceEntry = nullptr; michael@0: mStatement_CleanupUnmarked = nullptr; michael@0: mStatement_GatherEntries = nullptr; michael@0: mStatement_ActivateClient = nullptr; michael@0: mStatement_DeactivateGroup = nullptr; michael@0: mStatement_FindClient = nullptr; michael@0: mStatement_FindClientByNamespace = nullptr; michael@0: mStatement_EnumerateApps = nullptr; michael@0: mStatement_EnumerateGroups = nullptr; michael@0: mStatement_EnumerateGroupsTimeOrder = nullptr; michael@0: } michael@0: michael@0: // Close Database on the correct thread michael@0: bool isOnCurrentThread = true; michael@0: if (mInitThread) michael@0: mInitThread->IsOnCurrentThread(&isOnCurrentThread); michael@0: michael@0: if (!isOnCurrentThread) { michael@0: nsCOMPtr ev = new nsCloseDBEvent(mDB); michael@0: michael@0: if (ev) { michael@0: mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: else { michael@0: mDB->Close(); michael@0: } michael@0: michael@0: mDB = nullptr; michael@0: mInitThread = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: const char * michael@0: nsOfflineCacheDevice::GetDeviceID() michael@0: { michael@0: return OFFLINE_CACHE_DEVICE_ID; michael@0: } michael@0: michael@0: nsCacheEntry * michael@0: nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision) michael@0: { michael@0: mozilla::Telemetry::AutoTimer timer; michael@0: LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get())); michael@0: michael@0: // SELECT * FROM moz_cache WHERE key = ? michael@0: michael@0: // Decompose the key into "ClientID" and "Key" michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) michael@0: return nullptr; michael@0: michael@0: AutoResetStatement statement(mStatement_FindEntry); michael@0: michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid)); michael@0: nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: NS_ENSURE_SUCCESS(rv2, nullptr); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) michael@0: return nullptr; // entry not found michael@0: michael@0: nsOfflineCacheRecord rec; michael@0: statement->GetSharedBlob(0, &rec.metaDataLen, michael@0: (const uint8_t **) &rec.metaData); michael@0: rec.generation = statement->AsInt32(1); michael@0: rec.dataSize = statement->AsInt32(2); michael@0: rec.fetchCount = statement->AsInt32(3); michael@0: rec.lastFetched = statement->AsInt64(4); michael@0: rec.lastModified = statement->AsInt64(5); michael@0: rec.expirationTime = statement->AsInt64(6); michael@0: michael@0: LOG(("entry: [%u %d %d %d %lld %lld %lld]\n", michael@0: rec.metaDataLen, michael@0: rec.generation, michael@0: rec.dataSize, michael@0: rec.fetchCount, michael@0: rec.lastFetched, michael@0: rec.lastModified, michael@0: rec.expirationTime)); michael@0: michael@0: nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec); michael@0: michael@0: if (entry) michael@0: { michael@0: // make sure that the data file exists michael@0: nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data(); michael@0: bool isFile; michael@0: rv = binding->mDataFile->IsFile(&isFile); michael@0: if (NS_FAILED(rv) || !isFile) michael@0: { michael@0: DeleteEntry(entry, false); michael@0: delete entry; michael@0: return nullptr; michael@0: } michael@0: michael@0: // lock the entry michael@0: Lock(*fullKey); michael@0: } michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n", michael@0: entry->Key()->get())); michael@0: michael@0: // This method is called to inform us that the nsCacheEntry object is going michael@0: // away. We should persist anything that needs to be persisted, or if the michael@0: // entry is doomed, we can go ahead and clear its storage. michael@0: michael@0: if (entry->IsDoomed()) michael@0: { michael@0: // remove corresponding row and file if they exist michael@0: michael@0: // the row should have been removed in DoomEntry... we could assert that michael@0: // that happened. otherwise, all we have to do here is delete the file michael@0: // on disk. michael@0: DeleteData(entry); michael@0: } michael@0: else if (((nsOfflineCacheBinding *)entry->Data())->IsNewEntry()) michael@0: { michael@0: // UPDATE the database row michael@0: michael@0: // Only new entries are updated, since offline cache is updated in michael@0: // transactions. New entries are those who is returned from michael@0: // BindEntry(). michael@0: michael@0: LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n")); michael@0: UpdateEntry(entry); michael@0: } else { michael@0: LOG(("nsOfflineCacheDevice::DeactivateEntry " michael@0: "skipping update since entry is not dirty\n")); michael@0: } michael@0: michael@0: // Unlock the entry michael@0: Unlock(*entry->Key()); michael@0: michael@0: delete entry; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get())); michael@0: michael@0: NS_ENSURE_STATE(!entry->Data()); michael@0: michael@0: // This method is called to inform us that we have a new entry. The entry michael@0: // may collide with an existing entry in our DB, but if that happens we can michael@0: // assume that the entry is not being used. michael@0: michael@0: // INSERT the database row michael@0: michael@0: // XXX Assumption: if the row already exists, then FindEntry would have michael@0: // returned it. if that entry was doomed, then DoomEntry would have removed michael@0: // it from the table. so, we should always have to insert at this point. michael@0: michael@0: // Decompose the key into "ClientID" and "Key" michael@0: nsAutoCString keyBuf; michael@0: const char *cid, *key; michael@0: if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // create binding, pick best generation number michael@0: nsRefPtr binding = michael@0: nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1); michael@0: if (!binding) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: binding->MarkNewEntry(); michael@0: michael@0: nsOfflineCacheRecord rec; michael@0: rec.clientID = cid; michael@0: rec.key = key; michael@0: rec.metaData = nullptr; // don't write any metadata now. michael@0: rec.metaDataLen = 0; michael@0: rec.generation = binding->mGeneration; michael@0: rec.dataSize = 0; michael@0: rec.fetchCount = entry->FetchCount(); michael@0: rec.lastFetched = PRTimeFromSeconds(entry->LastFetched()); michael@0: rec.lastModified = PRTimeFromSeconds(entry->LastModified()); michael@0: rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime()); michael@0: michael@0: AutoResetStatement statement(mStatement_BindEntry); michael@0: michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID)); michael@0: nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key)); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt32ByIndex(3, rec.generation); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt32ByIndex(4, rec.dataSize); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt32ByIndex(5, rec.fetchCount); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(6, rec.lastFetched); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(7, rec.lastModified); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: tmp = statement->BindInt64ByIndex(8, rec.expirationTime); michael@0: if (NS_FAILED(tmp)) { michael@0: rv = tmp; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(!hasRows, "INSERT should not result in output"); michael@0: michael@0: entry->SetData(binding); michael@0: michael@0: // lock the entry michael@0: Lock(*entry->Key()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get())); michael@0: michael@0: // This method is called to inform us that we should mark the entry to be michael@0: // deleted when it is no longer in use. michael@0: michael@0: // We can go ahead and delete the corresponding row in our table, michael@0: // but we must not delete the file on disk until we are deactivated. michael@0: // In another word, the file should be deleted if the entry had been michael@0: // deactivated. michael@0: michael@0: DeleteEntry(entry, !entry->IsActive()); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry, michael@0: nsCacheAccessMode mode, michael@0: uint32_t offset, michael@0: nsIInputStream **result) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n", michael@0: entry->Key()->get())); michael@0: michael@0: *result = nullptr; michael@0: michael@0: NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG); michael@0: michael@0: // return an input stream to the entry's data file. the stream michael@0: // may be read on a background thread. michael@0: michael@0: nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data(); michael@0: NS_ENSURE_STATE(binding); michael@0: michael@0: nsCOMPtr in; michael@0: NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY); michael@0: if (!in) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // respect |offset| param michael@0: if (offset != 0) michael@0: { michael@0: nsCOMPtr seekable = do_QueryInterface(in); michael@0: NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED); michael@0: michael@0: seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: } michael@0: michael@0: in.swap(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry, michael@0: nsCacheAccessMode mode, michael@0: uint32_t offset, michael@0: nsIOutputStream **result) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n", michael@0: entry->Key()->get())); michael@0: michael@0: *result = nullptr; michael@0: michael@0: NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG); michael@0: michael@0: // return an output stream to the entry's data file. we can assume michael@0: // that the output stream will only be used on the main thread. michael@0: michael@0: nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data(); michael@0: NS_ENSURE_STATE(binding); michael@0: michael@0: nsCOMPtr out; michael@0: NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile, michael@0: PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, michael@0: 00600); michael@0: if (!out) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // respect |offset| param michael@0: nsCOMPtr seekable = do_QueryInterface(out); michael@0: NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED); michael@0: if (offset != 0) michael@0: seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: michael@0: // truncate the file at the given offset michael@0: seekable->SetEOF(); michael@0: michael@0: nsCOMPtr bufferedOut; michael@0: nsresult rv = michael@0: NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bufferedOut.swap(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n", michael@0: entry->Key()->get())); michael@0: michael@0: nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data(); michael@0: NS_ENSURE_STATE(binding); michael@0: michael@0: NS_IF_ADDREF(*result = binding->mDataFile); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, int32_t deltaSize) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n", michael@0: entry->Key()->get(), deltaSize)); michael@0: michael@0: const int32_t DELTA_THRESHOLD = 1<<14; // 16k michael@0: michael@0: // called to notify us of an impending change in the total size of the michael@0: // specified entry. michael@0: michael@0: uint32_t oldSize = entry->DataSize(); michael@0: NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops"); michael@0: uint32_t newSize = int32_t(oldSize) + deltaSize; michael@0: UpdateEntrySize(entry, newSize); michael@0: michael@0: mDeltaCounter += deltaSize; // this may go negative michael@0: michael@0: if (mDeltaCounter >= DELTA_THRESHOLD) michael@0: { michael@0: if (CacheSize() > mCacheCapacity) { michael@0: // the entry will overrun the cache capacity, doom the entry michael@0: // and abort michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: nsCacheService::DoomEntry(entry); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed."); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: mDeltaCounter = 0; // reset counter michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor) michael@0: { michael@0: NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // called to enumerate the offline cache. michael@0: michael@0: nsCOMPtr deviceInfo = michael@0: new nsOfflineCacheDeviceInfo(this); michael@0: michael@0: bool keepGoing; michael@0: nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo, michael@0: &keepGoing); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!keepGoing) michael@0: return NS_OK; michael@0: michael@0: // SELECT * from moz_cache; michael@0: michael@0: nsOfflineCacheRecord rec; michael@0: nsRefPtr info = new nsOfflineCacheEntryInfo; michael@0: if (!info) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: info->mRec = &rec; michael@0: michael@0: // XXX may want to list columns explicitly michael@0: nsCOMPtr statement; michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: for (;;) michael@0: { michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) michael@0: break; michael@0: michael@0: statement->GetSharedUTF8String(0, nullptr, &rec.clientID); michael@0: statement->GetSharedUTF8String(1, nullptr, &rec.key); michael@0: statement->GetSharedBlob(2, &rec.metaDataLen, michael@0: (const uint8_t **) &rec.metaData); michael@0: rec.generation = statement->AsInt32(3); michael@0: rec.dataSize = statement->AsInt32(4); michael@0: rec.fetchCount = statement->AsInt32(5); michael@0: rec.lastFetched = statement->AsInt64(6); michael@0: rec.lastModified = statement->AsInt64(7); michael@0: rec.expirationTime = statement->AsInt64(8); michael@0: michael@0: bool keepGoing; michael@0: rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing); michael@0: if (NS_FAILED(rv) || !keepGoing) michael@0: break; michael@0: } michael@0: michael@0: info->mRec = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::EvictEntries(const char *clientID) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n", michael@0: clientID ? clientID : "")); michael@0: michael@0: // called to evict all entries matching the given clientID. michael@0: michael@0: // need trigger to fire user defined function after a row is deleted michael@0: // so we can delete the corresponding data file. michael@0: EvictionObserver evictionObserver(mDB, mEvictionFunction); michael@0: michael@0: nsCOMPtr statement; michael@0: nsresult rv; michael@0: if (clientID) michael@0: { michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: evictionObserver.Apply(); michael@0: michael@0: statement = nullptr; michael@0: // Also evict any namespaces associated with this clientID. michael@0: if (clientID) michael@0: { michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"), michael@0: getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::MarkEntry(const nsCString &clientID, michael@0: const nsACString &key, michael@0: uint32_t typeBits) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n", michael@0: clientID.get(), PromiseFlatCString(key).get(), typeBits)); michael@0: michael@0: AutoResetStatement statement(mStatement_MarkEntry); michael@0: nsresult rv = statement->BindInt32ByIndex(0, typeBits); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(1, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(2, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID, michael@0: const nsACString &key, michael@0: uint32_t typeBits) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n", michael@0: clientID.get(), PromiseFlatCString(key).get(), typeBits)); michael@0: michael@0: AutoResetStatement statement(mStatement_UnmarkEntry); michael@0: nsresult rv = statement->BindInt32ByIndex(0, typeBits); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(1, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(2, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Remove the entry if it is now empty. michael@0: michael@0: EvictionObserver evictionObserver(mDB, mEvictionFunction); michael@0: michael@0: AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked); michael@0: rv = cleanupStatement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = cleanupStatement->BindUTF8StringByIndex(1, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cleanupStatement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: evictionObserver.Apply(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID, michael@0: const nsACString &key, michael@0: nsIApplicationCacheNamespace **out) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n", michael@0: clientID.get(), PromiseFlatCString(key).get())); michael@0: michael@0: nsresult rv; michael@0: michael@0: AutoResetStatement statement(mStatement_FindNamespaceEntry); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(1, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *out = nullptr; michael@0: michael@0: bool found = false; michael@0: nsCString nsSpec; michael@0: int32_t nsType = 0; michael@0: nsCString nsData; michael@0: michael@0: while (hasRows) michael@0: { michael@0: int32_t itemType; michael@0: rv = statement->GetInt32(2, &itemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!found || itemType > nsType) michael@0: { michael@0: nsType = itemType; michael@0: michael@0: rv = statement->GetUTF8String(0, nsSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->GetUTF8String(1, nsData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: found = true; michael@0: } michael@0: michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (found) { michael@0: nsCOMPtr ns = michael@0: new nsApplicationCacheNamespace(); michael@0: if (!ns) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: rv = ns->Init(nsType, nsSpec, nsData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ns.swap(*out); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID, michael@0: const nsACString &key) michael@0: { michael@0: // XXX: We should also be propagating this cache entry to other matching michael@0: // caches. See bug 444807. michael@0: michael@0: return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetTypes(const nsCString &clientID, michael@0: const nsACString &key, michael@0: uint32_t *typeBits) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n", michael@0: clientID.get(), PromiseFlatCString(key).get())); michael@0: michael@0: AutoResetStatement statement(mStatement_GetTypes); michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(1, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!hasRows) michael@0: return NS_ERROR_CACHE_KEY_NOT_FOUND; michael@0: michael@0: *typeBits = statement->AsInt32(0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GatherEntries(const nsCString &clientID, michael@0: uint32_t typeBits, michael@0: uint32_t *count, michael@0: char ***keys) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n", michael@0: clientID.get(), typeBits)); michael@0: michael@0: AutoResetStatement statement(mStatement_GatherEntries); michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindInt32ByIndex(1, typeBits); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::AddNamespace(const nsCString &clientID, michael@0: nsIApplicationCacheNamespace *ns) michael@0: { michael@0: nsCString namespaceSpec; michael@0: nsresult rv = ns->GetNamespaceSpec(namespaceSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString data; michael@0: rv = ns->GetData(data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t itemType; michael@0: rv = ns->GetItemType(&itemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]", michael@0: clientID.get(), namespaceSpec.get(), data.get(), itemType)); michael@0: michael@0: AutoResetStatement statement(mStatement_InsertNamespaceEntry); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(1, namespaceSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindUTF8StringByIndex(2, data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->BindInt32ByIndex(3, itemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetUsage(const nsACString &clientID, michael@0: uint32_t *usage) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n", michael@0: PromiseFlatCString(clientID).get())); michael@0: michael@0: *usage = 0; michael@0: michael@0: AutoResetStatement statement(mStatement_ApplicationCacheSize); michael@0: michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!hasRows) michael@0: return NS_OK; michael@0: michael@0: *usage = static_cast(statement->AsInt32(0)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetGroups(uint32_t *count, michael@0: char ***keys) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetGroups")); michael@0: michael@0: return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count, michael@0: char ***keys) michael@0: { michael@0: LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder")); michael@0: michael@0: return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys); michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheDevice::IsLocked(const nsACString &key) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return mLockedEntries.GetEntry(key); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheDevice::Lock(const nsACString &key) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mLockedEntries.PutEntry(key); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheDevice::Unlock(const nsACString &key) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mLockedEntries.RemoveEntry(key); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement, michael@0: uint32_t resultIndex, michael@0: uint32_t * count, michael@0: char *** values) michael@0: { michael@0: bool hasRows; michael@0: nsresult rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsTArray valArray; michael@0: while (hasRows) michael@0: { michael@0: uint32_t length; michael@0: valArray.AppendElement( michael@0: nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length))); michael@0: michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: *count = valArray.Length(); michael@0: char **ret = static_cast(NS_Alloc(*count * sizeof(char*))); michael@0: if (!ret) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: for (uint32_t i = 0; i < *count; i++) { michael@0: ret[i] = NS_strdup(valArray[i].get()); michael@0: if (!ret[i]) { michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: *values = ret; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group, michael@0: nsIApplicationCache **out) michael@0: { michael@0: *out = nullptr; michael@0: michael@0: nsCString clientID; michael@0: // Some characters are special in the clientID. Escape the groupID michael@0: // before putting it in to the client key. michael@0: if (!NS_Escape(nsCString(group), clientID, url_Path)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: michael@0: // Include the timestamp to guarantee uniqueness across runs, and michael@0: // the gNextTemporaryClientID for uniqueness within a second. michael@0: clientID.Append(nsPrintfCString("|%016lld|%d", michael@0: now / PR_USEC_PER_SEC, michael@0: gNextTemporaryClientID++)); michael@0: michael@0: nsCOMPtr cache = new nsApplicationCache(this, michael@0: group, michael@0: clientID); michael@0: if (!cache) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsCOMPtr weak = do_GetWeakReference(cache); michael@0: if (!weak) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: mCaches.Put(clientID, weak); michael@0: michael@0: cache.swap(*out); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID, michael@0: nsIApplicationCache **out) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: return GetApplicationCache_Unlocked(clientID, out); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetApplicationCache_Unlocked(const nsACString &clientID, michael@0: nsIApplicationCache **out) michael@0: { michael@0: *out = nullptr; michael@0: michael@0: nsCOMPtr cache; michael@0: michael@0: nsWeakPtr weak; michael@0: if (mCaches.Get(clientID, getter_AddRefs(weak))) michael@0: cache = do_QueryReferent(weak); michael@0: michael@0: if (!cache) michael@0: { michael@0: nsCString group; michael@0: nsresult rv = GetGroupForCache(clientID, group); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (group.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: cache = new nsApplicationCache(this, group, clientID); michael@0: weak = do_GetWeakReference(cache); michael@0: if (!weak) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mCaches.Put(clientID, weak); michael@0: } michael@0: michael@0: cache.swap(*out); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::GetActiveCache(const nsACString &group, michael@0: nsIApplicationCache **out) michael@0: { michael@0: *out = nullptr; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsCString *clientID; michael@0: if (mActiveCachesByGroup.Get(group, &clientID)) michael@0: return GetApplicationCache_Unlocked(*clientID, out); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::DeactivateGroup(const nsACString &group) michael@0: { michael@0: nsCString *active = nullptr; michael@0: michael@0: AutoResetStatement statement(mStatement_DeactivateGroup); michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, group); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (mActiveCachesByGroup.Get(group, &active)) michael@0: { michael@0: mActiveCaches.RemoveEntry(*active); michael@0: mActiveCachesByGroup.Remove(group); michael@0: active = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::DiscardByAppId(int32_t appID, bool browserEntriesOnly) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsAutoCString jaridsuffix; michael@0: jaridsuffix.Append('%'); michael@0: rv = AppendJARIdentifier(jaridsuffix, appID, browserEntriesOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: { michael@0: AutoResetStatement statement(mStatement_EnumerateApps); michael@0: rv = statement->BindUTF8StringByIndex(0, jaridsuffix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (hasRows) { michael@0: nsAutoCString group; michael@0: rv = statement->GetUTF8String(0, group); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString clientID; michael@0: rv = statement->GetUTF8String(1, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ev = michael@0: new nsOfflineCacheDiscardCache(this, group, clientID); michael@0: michael@0: rv = nsCacheService::DispatchToCacheIOThread(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: if (!browserEntriesOnly) { michael@0: // If deleting app, delete any 'inBrowserElement' entries too michael@0: rv = DiscardByAppId(appID, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, michael@0: const nsACString &clientID, michael@0: nsILoadContextInfo *loadContextInfo) michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: if (!mActiveCaches.Contains(clientID)) michael@0: return false; michael@0: } michael@0: michael@0: nsAutoCString groupID; michael@0: nsresult rv = GetGroupForCache(clientID, groupID); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsCOMPtr groupURI; michael@0: rv = NS_NewURI(getter_AddRefs(groupURI), groupID); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: // When we are choosing an initial cache to load the top michael@0: // level document from, the URL of that document must have michael@0: // the same origin as the manifest, according to the spec. michael@0: // The following check is here because explicit, fallback michael@0: // and dynamic entries might have origin different from the michael@0: // manifest origin. michael@0: if (!NS_SecurityCompareURIs(keyURI, groupURI, michael@0: GetStrictFileOriginPolicy())) michael@0: return false; michael@0: michael@0: // Get extended origin attributes michael@0: uint32_t appId = NECKO_NO_APP_ID; michael@0: bool isInBrowserElement = false; michael@0: michael@0: if (loadContextInfo) { michael@0: appId = loadContextInfo->AppId(); michael@0: isInBrowserElement = loadContextInfo->IsInBrowserElement(); michael@0: } michael@0: michael@0: // Check the groupID we found is equal to groupID based michael@0: // on the load context demanding load from app cache. michael@0: // This is check of extended origin. michael@0: nsAutoCString demandedGroupID; michael@0: rv = BuildApplicationCacheGroupID(groupURI, appId, isInBrowserElement, michael@0: demandedGroupID); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (groupID != demandedGroupID) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key, michael@0: nsILoadContextInfo *loadContextInfo, michael@0: nsIApplicationCache **out) michael@0: { michael@0: *out = nullptr; michael@0: michael@0: nsCOMPtr keyURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // First try to find a matching cache entry. michael@0: AutoResetStatement statement(mStatement_FindClient); michael@0: rv = statement->BindUTF8StringByIndex(0, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (hasRows) { michael@0: int32_t itemType; michael@0: rv = statement->GetInt32(1, &itemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) { michael@0: nsAutoCString clientID; michael@0: rv = statement->GetUTF8String(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (CanUseCache(keyURI, clientID, loadContextInfo)) { michael@0: return GetApplicationCache(clientID, out); michael@0: } michael@0: } michael@0: michael@0: rv = statement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // OK, we didn't find an exact match. Search for a client with a michael@0: // matching namespace. michael@0: michael@0: AutoResetStatement nsstatement(mStatement_FindClientByNamespace); michael@0: michael@0: rv = nsstatement->BindUTF8StringByIndex(0, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = nsstatement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (hasRows) michael@0: { michael@0: int32_t itemType; michael@0: rv = nsstatement->GetInt32(1, &itemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Don't associate with a cache based solely on a whitelist entry michael@0: if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) { michael@0: nsAutoCString clientID; michael@0: rv = nsstatement->GetUTF8String(0, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (CanUseCache(keyURI, clientID, loadContextInfo)) { michael@0: return GetApplicationCache(clientID, out); michael@0: } michael@0: } michael@0: michael@0: rv = nsstatement->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache, michael@0: const nsACString &key) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(cache); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsAutoCString clientID; michael@0: rv = cache->GetClientID(clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return CacheOpportunistically(clientID, key); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group, michael@0: const nsCSubstring &clientID) michael@0: { michael@0: AutoResetStatement statement(mStatement_ActivateClient); michael@0: nsresult rv = statement->BindUTF8StringByIndex(0, group); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindUTF8StringByIndex(1, clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now())); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = statement->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsCString *active; michael@0: if (mActiveCachesByGroup.Get(group, &active)) michael@0: { michael@0: mActiveCaches.RemoveEntry(*active); michael@0: mActiveCachesByGroup.Remove(group); michael@0: active = nullptr; michael@0: } michael@0: michael@0: if (!clientID.IsEmpty()) michael@0: { michael@0: mActiveCaches.PutEntry(clientID); michael@0: mActiveCachesByGroup.Put(group, new nsCString(clientID)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group, michael@0: const nsCSubstring &clientID) michael@0: { michael@0: nsCString *active = nullptr; michael@0: MutexAutoLock lock(mLock); michael@0: return mActiveCachesByGroup.Get(group, &active) && *active == clientID; michael@0: } michael@0: michael@0: /** michael@0: * Preference accessors michael@0: */ michael@0: michael@0: void michael@0: nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir) michael@0: { michael@0: if (Initialized()) michael@0: { michael@0: NS_ERROR("cannot switch cache directory once initialized"); michael@0: return; michael@0: } michael@0: michael@0: if (!parentDir) michael@0: { michael@0: mCacheDirectory = nullptr; michael@0: return; michael@0: } michael@0: michael@0: // ensure parent directory exists michael@0: nsresult rv = EnsureDir(parentDir); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: NS_WARNING("unable to create parent directory"); michael@0: return; michael@0: } michael@0: michael@0: mBaseDirectory = parentDir; michael@0: michael@0: // cache dir may not exist, but that's ok michael@0: nsCOMPtr dir; michael@0: rv = parentDir->Clone(getter_AddRefs(dir)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache")); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: mCacheDirectory = do_QueryInterface(dir); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheDevice::SetCapacity(uint32_t capacity) michael@0: { michael@0: mCacheCapacity = capacity * 1024; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache) michael@0: { michael@0: if (!mAutoShutdown) michael@0: return false; michael@0: michael@0: mAutoShutdown = false; michael@0: michael@0: Shutdown(); michael@0: michael@0: nsRefPtr cacheService = nsCacheService::GlobalInstance(); michael@0: cacheService->RemoveCustomOfflineDevice(this); michael@0: michael@0: nsAutoCString clientID; michael@0: aAppCache->GetClientID(clientID); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: mCaches.Remove(clientID); michael@0: michael@0: return true; michael@0: }