Files
tubestation/netwerk/cache/nsDiskCacheDeviceSQL.cpp
Wes Kocher 2a7ed059f1 Backed out 14 changesets (bug 1165515) for b2g mochitest-6 permafail CLOSED TREE
Backed out changeset 9b97e2aa2ed9 (bug 1165515)
Backed out changeset 150606c022a2 (bug 1165515)
Backed out changeset 4e875a488349 (bug 1165515)
Backed out changeset 467e7feeb546 (bug 1165515)
Backed out changeset d6b6cc373197 (bug 1165515)
Backed out changeset 0615265b593c (bug 1165515)
Backed out changeset fafd1dce9f08 (bug 1165515)
Backed out changeset d1df869245f9 (bug 1165515)
Backed out changeset 6876a7c63611 (bug 1165515)
Backed out changeset b7841c94a9a3 (bug 1165515)
Backed out changeset e5e3617f7c73 (bug 1165515)
Backed out changeset 39be3db95978 (bug 1165515)
Backed out changeset 0ec74176f8de (bug 1165515)
Backed out changeset 5b928dd10d71 (bug 1165515)
2015-06-01 17:57:58 -07:00

2728 lines
74 KiB
C++

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