Files
tubestation/netwerk/cache/nsDiskCacheDeviceSQL.cpp
Thinker K.F. Li ad97caa5fa Bug 769291 - Move nsOfflineCacheDevice::Discard() to cache IO thread. r=honzab
Discard() is called from destructor of nsApplicationCache.  Instances
of nsApplicationCache is released by last reference, it can be in main
thread.  We move Discard() tasks to cache IO thread to avoid blocking
the main thread.
2012-07-11 14:16:00 +08:00

2421 lines
67 KiB
C++

/* 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/Util.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 "nsThreadUtils.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozIStorageFunction.h"
#include "mozStorageHelper.h"
#include "nsICacheVisitor.h"
#include "nsISeekableStream.h"
#include "mozilla/FunctionTimer.h"
#include "mozilla/Telemetry.h"
using namespace mozilla;
static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
#define LOG(args) CACHE_LOG_DEBUG(args)
static PRUint32 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;
PRInt32 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:
AutoResetStatement(mozIStorageStatement *s)
: mStatement(s) {}
~AutoResetStatement() { mStatement->Reset(); }
mozIStorageStatement *operator->() { 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 AFTER DELETE"
" ON moz_cache FOR EACH ROW BEGIN SELECT"
" cache_eviction_observer("
" 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 LL_MAXINT
#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 PRUint64
DCacheHash(const char * key)
{
// initval 0x7416f295 was chosen randomly
return (PRUint64(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
}
/******************************************************************************
* nsOfflineCacheEvictionFunction
*/
NS_IMPL_THREADSAFE_ISUPPORTS1(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;
PRUint64 hash = DCacheHash(key);
PRUint32 dir1 = (PRUint32) (hash & 0x0F);
PRUint32 dir2 = (PRUint32)((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 = nsnull;
PRUint32 numEntries;
nsresult rv = values->GetNumEntries(&numEntries);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
PRUint32 valueLen;
const char *key = values->AsSharedUTF8String(0, &valueLen);
int generation = values->AsInt32(1);
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 (PRInt32 i = 0; i < mItems.Count(); i++) {
#if defined(PR_LOGGING)
nsCAutoString path;
mItems[i]->GetNativePath(path);
LOG((" removing %s\n", path.get()));
#endif
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 MOZ_FINAL : public nsICacheDeviceInfo
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICACHEDEVICEINFO
nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
: mDevice(device)
{}
private:
nsOfflineCacheDevice* mDevice;
};
NS_IMPL_ISUPPORTS1(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)
{
nsCAutoString 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(PRUint32 *aEntryCount)
{
*aEntryCount = mDevice->EntryCount();
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
{
*aTotalSize = mDevice->CacheSize();
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
{
*aMaximumSize = mDevice->CacheCapacity();
return NS_OK;
}
/******************************************************************************
* nsOfflineCacheBinding
*/
class nsOfflineCacheBinding MOZ_FINAL : public nsISupports
{
public:
NS_DECL_ISUPPORTS
static nsOfflineCacheBinding *
Create(nsIFile *cacheDir, const nsCString *key, int generation);
nsCOMPtr<nsIFile> mDataFile;
int mGeneration;
};
NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheBinding)
nsOfflineCacheBinding *
nsOfflineCacheBinding::Create(nsIFile *cacheDir,
const nsCString *fullKey,
int generation)
{
nsCOMPtr<nsIFile> file;
cacheDir->Clone(getter_AddRefs(file));
if (!file)
return nsnull;
nsCAutoString keyBuf;
const char *cid, *key;
if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
return nsnull;
PRUint64 hash = DCacheHash(key);
PRUint32 dir1 = (PRUint32) (hash & 0x0F);
PRUint32 dir2 = (PRUint32)((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 nsnull;
rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
return nsnull;
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 nsnull;
}
nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
if (!binding)
return nsnull;
binding->mDataFile.swap(file);
binding->mGeneration = generation;
return binding;
}
/******************************************************************************
* nsOfflineCacheRecord
*/
struct nsOfflineCacheRecord
{
const char *clientID;
const char *key;
const PRUint8 *metaData;
PRUint32 metaDataLen;
PRInt32 generation;
PRInt32 flags;
PRInt32 dataSize;
PRInt32 fetchCount;
PRInt64 lastFetched;
PRInt64 lastModified;
PRInt64 expirationTime;
};
static nsCacheEntry *
CreateCacheEntry(nsOfflineCacheDevice *device,
const nsCString *fullKey,
const nsOfflineCacheRecord &rec)
{
if (rec.flags != 0)
{
LOG(("refusing to load busy entry\n"));
return nsnull;
}
nsCacheEntry *entry;
nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
nsICache::STREAM_BASED,
nsICache::STORE_OFFLINE,
device, &entry);
if (NS_FAILED(rv))
return nsnull;
entry->SetFetchCount((PRUint32) rec.fetchCount);
entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
entry->SetDataSize((PRUint32) rec.dataSize);
entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
// create a binding object for this entry
nsOfflineCacheBinding *binding =
nsOfflineCacheBinding::Create(device->CacheDirectory(),
fullKey,
rec.generation);
if (!binding)
{
delete entry;
return nsnull;
}
entry->SetData(binding);
return entry;
}
/******************************************************************************
* nsOfflineCacheEntryInfo
*/
class nsOfflineCacheEntryInfo MOZ_FINAL : public nsICacheEntryInfo
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICACHEENTRYINFO
nsOfflineCacheRecord *mRec;
};
NS_IMPL_ISUPPORTS1(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(PRInt32 *aFetchCount)
{
*aFetchCount = mRec->fetchCount;
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched)
{
*aLastFetched = SecondsFromPRTime(mRec->lastFetched);
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheEntryInfo::GetLastModified(PRUint32 *aLastModified)
{
*aLastModified = SecondsFromPRTime(mRec->lastModified);
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime)
{
*aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
{
*aStreamBased = true;
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
{
*aDataSize = mRec->dataSize;
return NS_OK;
}
/******************************************************************************
* nsApplicationCacheNamespace
*/
NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
NS_IMETHODIMP
nsApplicationCacheNamespace::Init(PRUint32 itemType,
const nsACString &namespaceSpec,
const nsACString &data)
{
mItemType = itemType;
mNamespaceSpec = namespaceSpec;
mData = data;
return NS_OK;
}
NS_IMETHODIMP
nsApplicationCacheNamespace::GetItemType(PRUint32 *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_ISUPPORTS2(nsApplicationCache,
nsIApplicationCache,
nsISupportsWeakReference)
nsApplicationCache::nsApplicationCache()
: mDevice(nsnull)
, 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;
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::GetGroupID(nsACString &out)
{
out = mGroup;
return NS_OK;
}
NS_IMETHODIMP
nsApplicationCache::GetClientID(nsACString &out)
{
out = mClientID;
return NS_OK;
}
NS_IMETHODIMP
nsApplicationCache::GetCacheDirectory(nsIFile **out)
{
if (mDevice->BaseDirectory())
NS_ADDREF(*out = mDevice->BaseDirectory());
else
*out = nsnull;
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);
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;
nsRefPtr<nsIRunnable> ev =
new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
return rv;
}
NS_IMETHODIMP
nsApplicationCache::MarkEntry(const nsACString &key,
PRUint32 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,
PRUint32 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,
PRUint32 *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(PRUint32 typeBits,
PRUint32 * 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);
PRUint32 length;
nsresult rv = namespaces->GetLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
for (PRUint32 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(PRUint32 *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:
nsCloseDBEvent(mozIStorageConnection *aDB)
{
mDB = aDB;
}
NS_IMETHOD Run()
{
mDB->Close();
return NS_OK;
}
protected:
virtual ~nsCloseDBEvent() {}
private:
nsCOMPtr<mozIStorageConnection> mDB;
};
/******************************************************************************
* nsOfflineCacheDevice
*/
NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheDevice)
nsOfflineCacheDevice::nsOfflineCacheDevice()
: mDB(nsnull)
, mCacheCapacity(0)
, mDeltaCounter(0)
{
}
/* 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;
}
PRUint32
nsOfflineCacheDevice::CacheSize()
{
AutoResetStatement statement(mStatement_CacheSize);
bool hasRows;
nsresult rv = statement->ExecuteStep(&hasRows);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
return (PRUint32) statement->AsInt32(0);
}
PRUint32
nsOfflineCacheDevice::EntryCount()
{
AutoResetStatement statement(mStatement_EntryCount);
bool hasRows;
nsresult rv = statement->ExecuteStep(&hasRows);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
return (PRUint32) statement->AsInt32(0);
}
nsresult
nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
{
// Decompose the key into "ClientID" and "Key"
nsCAutoString keyBuf;
const char *cid, *key;
if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
return NS_ERROR_UNEXPECTED;
nsCString metaDataBuf;
PRUint32 mdSize = entry->MetaDataSize();
if (!EnsureStringLength(metaDataBuf, mdSize))
return NS_ERROR_OUT_OF_MEMORY;
char *md = metaDataBuf.BeginWriting();
entry->FlattenMetaData(md, mdSize);
nsOfflineCacheRecord rec;
rec.metaData = (const PRUint8 *) md;
rec.metaDataLen = mdSize;
rec.flags = 0; // mark entry as inactive
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);
rv |= statement->BindInt32ByIndex(1, rec.flags);
rv |= statement->BindInt32ByIndex(2, rec.dataSize);
rv |= statement->BindInt32ByIndex(3, rec.fetchCount);
rv |= statement->BindInt64ByIndex(4, rec.lastFetched);
rv |= statement->BindInt64ByIndex(5, rec.lastModified);
rv |= statement->BindInt64ByIndex(6, rec.expirationTime);
rv |= statement->BindUTF8StringByIndex(7, nsDependentCString(cid));
rv |= statement->BindUTF8StringByIndex(8, nsDependentCString(key));
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, PRUint32 newSize)
{
// Decompose the key into "ClientID" and "Key"
nsCAutoString keyBuf;
const char *cid, *key;
if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
return NS_ERROR_UNEXPECTED;
AutoResetStatement statement(mStatement_UpdateEntrySize);
nsresult rv;
rv = statement->BindInt32ByIndex(0, newSize);
rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
rv |= statement->BindUTF8StringByIndex(2, nsDependentCString(key));
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"
nsCAutoString keyBuf;
const char *cid, *key;
if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
return NS_ERROR_UNEXPECTED;
AutoResetStatement statement(mStatement_DeleteEntry);
nsresult rv;
rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(key));
NS_ENSURE_SUCCESS(rv, rv);
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()
{
NS_TIME_FUNCTION;
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);
nsCOMPtr<mozIStorageService> ss =
do_GetService("@mozilla.org/storage/service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
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.
// "Flags" is a bit-field indicating the state of the entry.
//
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"
" Flags 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"), 2, 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 = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
StatementSql ( mStatement_UpdateEntryFlags, "UPDATE moz_cache SET Flags = ? WHERE ClientID = ? AND Key = ?;" ),
StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, Flags, 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_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
};
for (PRUint32 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);
}
// Clear up any dangling active flags
rv = mDB->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("UPDATE moz_cache"
" SET Flags=(Flags & ~1)"
" WHERE (Flags & 1);"));
NS_ENSURE_SUCCESS(rv, rv);
rv = InitActiveCaches();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsOfflineCacheDevice::InitActiveCaches()
{
mCaches.Init();
mActiveCachesByGroup.Init();
mActiveCaches.Init(5);
AutoResetStatement statement(mStatement_EnumerateGroups);
bool hasRows;
nsresult rv = statement->ExecuteStep(&hasRows);
NS_ENSURE_SUCCESS(rv, rv);
while (hasRows)
{
nsCAutoString 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);
if (mCaches.IsInitialized())
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 = nsnull;
mStatement_ApplicationCacheSize = nsnull;
mStatement_EntryCount = nsnull;
mStatement_UpdateEntry = nsnull;
mStatement_UpdateEntrySize = nsnull;
mStatement_UpdateEntryFlags = nsnull;
mStatement_DeleteEntry = nsnull;
mStatement_FindEntry = nsnull;
mStatement_BindEntry = nsnull;
mStatement_ClearDomain = nsnull;
mStatement_MarkEntry = nsnull;
mStatement_UnmarkEntry = nsnull;
mStatement_GetTypes = nsnull;
mStatement_FindNamespaceEntry = nsnull;
mStatement_InsertNamespaceEntry = nsnull;
mStatement_CleanupUnmarked = nsnull;
mStatement_GatherEntries = nsnull;
mStatement_ActivateClient = nsnull;
mStatement_DeactivateGroup = nsnull;
mStatement_FindClient = nsnull;
mStatement_FindClientByNamespace = nsnull;
mStatement_EnumerateGroups = nsnull;
mStatement_EnumerateGroupsTimeOrder = nsnull;
}
// 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 = nsnull;
mInitThread = nsnull;
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> timer;
LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
// SELECT * FROM moz_cache WHERE key = ?
// Decompose the key into "ClientID" and "Key"
nsCAutoString keyBuf;
const char *cid, *key;
if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
return nsnull;
AutoResetStatement statement(mStatement_FindEntry);
nsresult rv;
rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(key));
NS_ENSURE_SUCCESS(rv, nsnull);
bool hasRows;
rv = statement->ExecuteStep(&hasRows);
if (NS_FAILED(rv) || !hasRows)
return nsnull; // entry not found
nsOfflineCacheRecord rec;
statement->GetSharedBlob(0, &rec.metaDataLen,
(const PRUint8 **) &rec.metaData);
rec.generation = statement->AsInt32(1);
rec.flags = statement->AsInt32(2);
rec.dataSize = statement->AsInt32(3);
rec.fetchCount = statement->AsInt32(4);
rec.lastFetched = statement->AsInt64(5);
rec.lastModified = statement->AsInt64(6);
rec.expirationTime = statement->AsInt64(7);
LOG(("entry: [%u %d %d %d %d %lld %lld %lld]\n",
rec.metaDataLen,
rec.generation,
rec.flags,
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 nsnull;
}
statement->Reset();
// mark as active
AutoResetStatement updateStatement(mStatement_UpdateEntryFlags);
rec.flags |= 0x1;
rv |= updateStatement->BindInt32ByIndex(0, rec.flags);
rv |= updateStatement->BindUTF8StringByIndex(1, nsDependentCString(cid));
rv |= updateStatement->BindUTF8StringByIndex(2, nsDependentCString(key));
if (NS_FAILED(rv))
{
delete entry;
return nsnull;
}
rv = updateStatement->ExecuteStep(&hasRows);
if (NS_FAILED(rv))
{
delete entry;
return nsnull;
}
NS_ASSERTION(!hasRows, "UPDATE should not result in output");
}
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
{
// UPDATE the database row
// XXX Assumption: the row already exists because it was either created
// with a call to BindEntry or it was there when we called FindEntry.
UpdateEntry(entry);
}
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"
nsCAutoString 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;
nsOfflineCacheRecord rec;
rec.clientID = cid;
rec.key = key;
rec.metaData = NULL; // don't write any metadata now.
rec.metaDataLen = 0;
rec.generation = binding->mGeneration;
rec.flags = 0x1; // mark entry as active, we'll reset this in DeactivateEntry
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;
rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
rv |= statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
rv |= statement->BindInt32ByIndex(3, rec.generation);
rv |= statement->BindInt32ByIndex(4, rec.flags);
rv |= statement->BindInt32ByIndex(5, rec.dataSize);
rv |= statement->BindInt32ByIndex(6, rec.fetchCount);
rv |= statement->BindInt64ByIndex(7, rec.lastFetched);
rv |= statement->BindInt64ByIndex(8, rec.lastModified);
rv |= statement->BindInt64ByIndex(9, rec.expirationTime);
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);
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.
DeleteEntry(entry, false);
}
nsresult
nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
nsCacheAccessMode mode,
PRUint32 offset,
nsIInputStream **result)
{
LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
entry->Key()->get()));
*result = nsnull;
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,
PRUint32 offset,
nsIOutputStream **result)
{
LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
entry->Key()->get()));
*result = nsnull;
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;
NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
if (!bufferedOut)
return NS_ERROR_UNEXPECTED;
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, PRInt32 deltaSize)
{
LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
entry->Key()->get(), deltaSize));
const PRInt32 DELTA_THRESHOLD = 1<<14; // 16k
// called to notify us of an impending change in the total size of the
// specified entry.
PRUint32 oldSize = entry->DataSize();
NS_ASSERTION(deltaSize >= 0 || PRInt32(oldSize) + deltaSize >= 0, "oops");
PRUint32 newSize = PRInt32(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, NULL, &rec.clientID);
statement->GetSharedUTF8String(1, NULL, &rec.key);
statement->GetSharedBlob(2, &rec.metaDataLen,
(const PRUint8 **) &rec.metaData);
rec.generation = statement->AsInt32(3);
rec.flags = statement->AsInt32(4);
rec.dataSize = statement->AsInt32(5);
rec.fetchCount = statement->AsInt32(6);
rec.lastFetched = statement->AsInt64(7);
rec.lastModified = statement->AsInt64(8);
rec.expirationTime = statement->AsInt64(9);
bool keepGoing;
rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
if (NS_FAILED(rv) || !keepGoing)
break;
}
info->mRec = nsnull;
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=? AND Flags = 0;"),
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 WHERE Flags = 0;"),
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 = nsnull;
// 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,
PRUint32 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,
PRUint32 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 = nsnull;
bool found = false;
nsCString nsSpec;
PRInt32 nsType = 0;
nsCString nsData;
while (hasRows)
{
PRInt32 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,
PRUint32 *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,
PRUint32 typeBits,
PRUint32 *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);
PRUint32 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,
PRUint32 *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<PRUint32>(statement->AsInt32(0));
return NS_OK;
}
nsresult
nsOfflineCacheDevice::GetGroups(PRUint32 *count,
char ***keys)
{
LOG(("nsOfflineCacheDevice::GetGroups"));
return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
}
nsresult
nsOfflineCacheDevice::GetGroupsTimeOrdered(PRUint32 *count,
char ***keys)
{
LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
}
nsresult
nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
PRUint32 resultIndex,
PRUint32 * count,
char *** values)
{
bool hasRows;
nsresult rv = statement->ExecuteStep(&hasRows);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsCString> valArray;
while (hasRows)
{
PRUint32 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 **>(NS_Alloc(*count * sizeof(char*)));
if (!ret) return NS_ERROR_OUT_OF_MEMORY;
for (PRUint32 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 = nsnull;
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;
mCaches.Put(clientID, weak);
cache.swap(*out);
return NS_OK;
}
nsresult
nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
nsIApplicationCache **out)
{
*out = nsnull;
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 = nsnull;
nsCString *clientID;
if (mActiveCachesByGroup.Get(group, &clientID))
return GetApplicationCache(*clientID, out);
return NS_OK;
}
nsresult
nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
{
nsCString *active = nsnull;
AutoResetStatement statement(mStatement_DeactivateGroup);
nsresult rv = statement->BindUTF8StringByIndex(0, group);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
if (mActiveCachesByGroup.Get(group, &active))
{
mActiveCaches.RemoveEntry(*active);
mActiveCachesByGroup.Remove(group);
active = nsnull;
}
return NS_OK;
}
bool
nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, const nsCString &clientID)
{
if (mActiveCaches.Contains(clientID)) {
nsCAutoString groupID;
nsresult rv = GetGroupForCache(clientID, groupID);
NS_ENSURE_SUCCESS(rv, false);
nsCOMPtr<nsIURI> groupURI;
rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
if (NS_SUCCEEDED(rv)) {
// 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 true;
}
}
return false;
}
nsresult
nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
nsIApplicationCache **out)
{
*out = nsnull;
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) {
PRInt32 itemType;
rv = statement->GetInt32(1, &itemType);
NS_ENSURE_SUCCESS(rv, rv);
if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
nsCAutoString clientID;
rv = statement->GetUTF8String(0, clientID);
NS_ENSURE_SUCCESS(rv, rv);
if (CanUseCache(keyURI, clientID)) {
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)
{
PRInt32 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)) {
nsCAutoString clientID;
rv = nsstatement->GetUTF8String(0, clientID);
NS_ENSURE_SUCCESS(rv, rv);
if (CanUseCache(keyURI, clientID)) {
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;
nsCAutoString 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);
nsCString *active;
if (mActiveCachesByGroup.Get(group, &active))
{
mActiveCaches.RemoveEntry(*active);
mActiveCachesByGroup.Remove(group);
active = nsnull;
}
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 = nsnull;
return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
}
nsresult
nsOfflineCacheDevice::GetGroupForCache(const nsACString &clientID,
nsCString &out)
{
out.Assign(clientID);
out.Truncate(out.FindChar('|'));
NS_UnescapeURL(out);
return NS_OK;
}
/**
* Preference accessors
*/
void
nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
{
if (Initialized())
{
NS_ERROR("cannot switch cache directory once initialized");
return;
}
if (!parentDir)
{
mCacheDirectory = nsnull;
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(PRUint32 capacity)
{
mCacheCapacity = capacity * 1024;
}