Bug 916052 - Adapt about:cache to the HTTP cache v2 API, r=michal+MattN

This commit is contained in:
Honza Bambas 2014-05-01 13:28:12 +02:00
parent 56adb37c03
commit f58519de04
34 changed files with 1581 additions and 514 deletions

View File

@ -20,10 +20,11 @@ function test() {
{
info("disk storage contains " + num + " entries");
},
onCacheEntryInfo: function(entry)
onCacheEntryInfo: function(uri)
{
info(entry.key);
is(entry.key.contains(filename), false, "web content present in disk cache");
var urispec = uri.asciiSpec;
info(urispec);
is(urispec.contains(filename), false, "web content present in disk cache");
},
onCacheEntryVisitCompleted: function()
{

View File

@ -87,10 +87,11 @@ function getStorageEntryCount(device, goon) {
entryCount: 0,
onCacheStorageInfo: function (aEntryCount, aConsumption) {
},
onCacheEntryInfo: function(entry)
onCacheEntryInfo: function(uri)
{
info(device + ":" + entry.key + "\n");
if (entry.key.match(/^http:\/\/example.org\//))
var urispec = uri.asciiSpec;
info(device + ":" + urispec + "\n");
if (urispec.match(/^http:\/\/example.org\//))
++this.entryCount;
},
onCacheEntryVisitCompleted: function()

View File

@ -85,10 +85,11 @@ function checkDiskCacheFor(host, done)
{
info("disk storage contains " + num + " entries");
},
onCacheEntryInfo: function(entry)
onCacheEntryInfo: function(uri)
{
info(entry.key);
foundPrivateData |= entry.key.contains(host);
var urispec = uri.asciiSpec;
info(urispec);
foundPrivateData |= urispec.contains(host);
},
onCacheEntryVisitCompleted: function()
{

View File

@ -153,7 +153,18 @@ NS_IMETHODIMP AppCacheStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisito
LOG(("AppCacheStorage::AsyncVisitStorage [this=%p, cb=%p]", this, aVisitor));
return NS_ERROR_NOT_IMPLEMENTED;
nsresult rv;
nsCOMPtr<nsICacheService> serv =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
"offline", aVisitor, aVisitEntries, LoadInfo());
rv = serv->VisitEntries(cb);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
} // net

View File

@ -1165,6 +1165,13 @@ NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aVa
return mFile->SetElement(aKey, aValue);
}
NS_IMETHODIMP CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
{
NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
return mFile->VisitMetaData(aVisitor);
}
NS_IMETHODIMP CacheEntry::MetaDataReady()
{
mozilla::MutexAutoLock lock(mLock);
@ -1467,7 +1474,7 @@ void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
#endif
// Half-life is dynamic, in seconds.
static double half_life = CacheObserver::HalfLifeSeconds();
static double half_life = CacheObserver::HalfLifeSeconds();
// Must convert from seconds to milliseconds since PR_Now() gives usecs.
static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);

View File

@ -839,6 +839,17 @@ CacheFile::SetElement(const char *aKey, const char *aValue)
return mMetadata->SetElement(aKey, aValue);
}
nsresult
CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
{
CacheFileAutoLock lock(this);
MOZ_ASSERT(mMetadata);
MOZ_ASSERT(mReady);
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
return mMetadata->Visit(aVisitor);
}
nsresult
CacheFile::ElementsSize(uint32_t *_retval)
{

View File

@ -14,6 +14,7 @@
class nsIInputStream;
class nsIOutputStream;
class nsICacheEntryMetaDataVisitor;
namespace mozilla {
namespace net {
@ -85,6 +86,7 @@ public:
// metadata forwarders
nsresult GetElement(const char *aKey, char **_retval);
nsresult SetElement(const char *aKey, const char *aValue);
nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor);
nsresult ElementsSize(uint32_t *_retval);
nsresult SetExpirationTime(uint32_t aExpirationTime);
nsresult GetExpirationTime(uint32_t *_retval);

View File

@ -19,6 +19,7 @@
#include "nsISimpleEnumerator.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIObserverService.h"
#include "nsICacheStorageVisitor.h"
#include "nsISizeOf.h"
#include "mozilla/Telemetry.h"
#include "mozilla/DebugOnly.h"
@ -28,6 +29,7 @@
#include "private/pprio.h"
#include "mozilla/VisualEventTracer.h"
#include "mozilla/Preferences.h"
#include "nsNetUtil.h"
// include files for ftruncate (or equivalent)
#if defined(XP_UNIX)
@ -2212,6 +2214,92 @@ void CacheFileIOManager::GetCacheDirectory(nsIFile** result)
file.forget(result);
}
// static
nsresult
CacheFileIOManager::GetEntryInfo(const SHA1Sum::Hash *aHash,
CacheStorageService::EntryInfoCallback *aCallback)
{
MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
nsresult rv;
nsRefPtr<CacheFileIOManager> ioMan = gInstance;
if (!ioMan) {
return NS_ERROR_NOT_INITIALIZED;
}
nsAutoCString enhanceId;
nsAutoCString uriSpec;
nsRefPtr<CacheFileHandle> handle;
ioMan->mHandles.GetHandle(aHash, false, getter_AddRefs(handle));
if (handle) {
nsRefPtr<nsILoadContextInfo> info =
CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
MOZ_ASSERT(info);
if (!info) {
return NS_OK; // ignore
}
nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
if (!service) {
return NS_ERROR_NOT_INITIALIZED;
}
// Invokes OnCacheEntryInfo when an existing entry is found
if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
return NS_OK;
}
// When we are here, there is no existing entry and we need
// to synchrnously load metadata from a disk file.
}
// Locate the actual file
nsCOMPtr<nsIFile> file;
ioMan->GetFile(aHash, getter_AddRefs(file));
// Read metadata from the file synchronously
nsRefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
rv = metadata->SyncReadMetadata(file);
if (NS_FAILED(rv)) {
return NS_OK;
}
// Now get the context + enhance id + URL from the key.
nsAutoCString key;
metadata->GetKey(key);
nsRefPtr<nsILoadContextInfo> info =
CacheFileUtils::ParseKey(key, &enhanceId, &uriSpec);
MOZ_ASSERT(info);
if (!info) {
return NS_OK;
}
// Pick all data to pass to the callback.
int64_t dataSize = metadata->Offset();
uint32_t fetchCount;
if (NS_FAILED(metadata->GetFetchCount(&fetchCount))) {
fetchCount = 0;
}
uint32_t expirationTime;
if (NS_FAILED(metadata->GetExpirationTime(&expirationTime))) {
expirationTime = 0;
}
uint32_t lastModified;
if (NS_FAILED(metadata->GetLastModified(&lastModified))) {
lastModified = 0;
}
// Call directly on the callback.
aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount,
lastModified, expirationTime);
return NS_OK;
}
static nsresult
TruncFile(PRFileDesc *aFD, uint32_t aEOF)
{

View File

@ -6,6 +6,7 @@
#define CacheFileIOManager__h__
#include "CacheIOThread.h"
#include "CacheStorageService.h"
#include "nsIEventTarget.h"
#include "nsITimer.h"
#include "nsCOMPtr.h"
@ -22,6 +23,7 @@ class nsIFile;
class nsITimer;
class nsIDirectoryEnumerator;
class nsILoadContextInfo;
class nsICacheStorageVisitor;
namespace mozilla {
namespace net {
@ -278,6 +280,13 @@ public:
static void GetCacheDirectory(nsIFile** result);
// Calls synchronously OnEntryInfo for an entry with the given hash.
// Tries to find an existing entry in the service hashtables first, if not
// found, loads synchronously from disk file.
// Callable on the IO thread only.
static nsresult GetEntryInfo(const SHA1Sum::Hash *aHash,
CacheStorageService::EntryInfoCallback *aCallback);
// Memory reporting
static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);

View File

@ -11,6 +11,7 @@
#include "CacheFileChunk.h"
#include "CacheFileUtils.h"
#include "nsILoadContextInfo.h"
#include "nsICacheEntry.h" // for nsICacheEntryMetaDataVisitor
#include "../cache/nsCacheUtils.h"
#include "nsIFile.h"
#include "mozilla/Telemetry.h"
@ -304,7 +305,10 @@ CacheFileMetadata::SyncReadMetadata(nsIFile *aFile)
int64_t fileSize;
rv = aFile->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(rv)) {
// Don't bloat the console
return rv;
}
PRFileDesc *fd;
rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
@ -433,6 +437,28 @@ CacheFileMetadata::SetElement(const char *aKey, const char *aValue)
return NS_OK;
}
nsresult
CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor *aVisitor)
{
const char *data = mBuf;
const char *limit = mBuf + mElementsSize;
while (data < limit) {
// Point to the value part
const char *value = data + strlen(data) + 1;
MOZ_ASSERT(value < limit, "Metadata elements corrupted");
aVisitor->OnMetaDataElement(data, value);
// Skip value part
data = value + strlen(value) + 1;
}
MOZ_ASSERT(data == limit, "Metadata elements corrupted");
return NS_OK;
}
CacheHash::Hash16_t
CacheFileMetadata::GetHash(uint32_t aIndex)
{

View File

@ -13,6 +13,8 @@
#include "nsAutoPtr.h"
#include "nsString.h"
class nsICacheEntryMetaDataVisitor;
namespace mozilla {
namespace net {
@ -22,7 +24,7 @@ namespace net {
// the frecency value to a correct internal representation again.
// It might not be 100% accurate, but for the purpose it suffice.
#define FRECENCY2INT(aFrecency) \
((uint32_t)(aFrecency * CacheObserver::HalfLifeSeconds()))
((uint32_t)((aFrecency) * CacheObserver::HalfLifeSeconds()))
#define INT2FRECENCY(aInt) \
((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
@ -129,6 +131,7 @@ public:
const char * GetElement(const char *aKey);
nsresult SetElement(const char *aKey, const char *aValue);
nsresult Visit(nsICacheEntryMetaDataVisitor *aVisitor);
CacheHash::Hash16_t GetHash(uint32_t aIndex);
nsresult SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);

View File

@ -1205,6 +1205,43 @@ CacheIndex::GetCacheSize(uint32_t *_retval)
return NS_OK;
}
// static
nsresult
CacheIndex::GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount)
{
LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo));
nsRefPtr<CacheIndex> index = gInstance;
if (!index) {
return NS_ERROR_NOT_INITIALIZED;
}
CacheIndexAutoLock lock(index);
if (!index->IsIndexUsable()) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!aInfo) {
return NS_ERROR_INVALID_ARG;
}
*aSize = 0;
*aCount = 0;
for (uint32_t i = 0; i < index->mFrecencyArray.Length(); ++i) {
CacheIndexRecord* record = index->mFrecencyArray[i];
if (!CacheIndexEntry::RecordMatchesLoadContextInfo(record, aInfo))
continue;
*aSize += CacheIndexEntry::GetFileSize(record);
++*aCount;
}
return NS_OK;
}
// static
nsresult
CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver)

View File

@ -205,7 +205,11 @@ public:
mRec->mFlags |= aFileSize;
}
// Returns filesize in kilobytes.
uint32_t GetFileSize() { return mRec->mFlags & kFileSizeMask; }
uint32_t GetFileSize() { return GetFileSize(mRec); }
static uint32_t GetFileSize(CacheIndexRecord *aRec)
{
return aRec->mFlags & kFileSizeMask;
}
bool IsFileEmpty() { return GetFileSize() == 0; }
void WriteToBuf(void *aBuf)
@ -563,6 +567,10 @@ public:
// Returns cache size in kB.
static nsresult GetCacheSize(uint32_t *_retval);
// Synchronously returns the disk occupation and number of entries per-context.
// Callable on any thread.
static nsresult GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount);
// Asynchronously gets the disk cache size, used for display in the UI.
static nsresult AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver);

View File

@ -2,8 +2,8 @@
* 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/. */
#ifndef CacheLog__h__
#define CacheLog__h__
#ifndef Cache2Log__h__
#define Cache2Log__h__
#if defined(MOZ_LOGGING)
#define FORCE_PR_LOG

View File

@ -7,9 +7,7 @@
#include "CacheFileIOManager.h"
#include "CacheObserver.h"
#include "CacheIndex.h"
#include "nsICacheStorageVisitor.h"
#include "nsIObserverService.h"
#include "CacheIndexIterator.h"
#include "CacheStorage.h"
#include "AppCacheStorage.h"
#include "CacheEntry.h"
@ -19,12 +17,16 @@
#include "nsCacheService.h"
#include "nsDeleteDir.h"
#include "nsICacheStorageVisitor.h"
#include "nsIObserverService.h"
#include "nsIFile.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsWeakReference.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/VisualEventTracer.h"
@ -163,35 +165,57 @@ void CacheStorageService::ShutdownBackground()
namespace { // anon
// WalkRunnable
// Responsible to visit the storage and walk all entries on it asynchronously
class WalkRunnable : public nsRunnable
// WalkCacheRunnable
// Base class for particular storage entries visiting
class WalkCacheRunnable : public nsRunnable
, public CacheStorageService::EntryInfoCallback
{
public:
WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
bool aUsingDisk,
nsICacheStorageVisitor* aVisitor)
: mContextKey(aContextKey)
protected:
WalkCacheRunnable(nsICacheStorageVisitor* aVisitor,
bool aVisitEntries)
: mService(CacheStorageService::Self())
, mCallback(aVisitor)
, mSize(0)
, mNotifyStorage(true)
, mVisitEntries(aVisitEntries)
, mUsingDisk(aUsingDisk)
{
}
nsRefPtr<CacheStorageService> mService;
nsCOMPtr<nsICacheStorageVisitor> mCallback;
uint64_t mSize;
bool mNotifyStorage : 1;
bool mVisitEntries : 1;
};
// WalkMemoryCacheRunnable
// Responsible to visit memory storage and walk
// all entries on it asynchronously.
class WalkMemoryCacheRunnable : public WalkCacheRunnable
{
public:
WalkMemoryCacheRunnable(nsILoadContextInfo *aLoadInfo,
bool aVisitEntries,
nsICacheStorageVisitor* aVisitor)
: WalkCacheRunnable(aVisitor, aVisitEntries)
{
CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
MOZ_ASSERT(NS_IsMainThread());
}
nsresult Walk()
{
return mService->Dispatch(this);
}
private:
NS_IMETHODIMP Run()
{
if (CacheStorageService::IsOnManagementThread()) {
LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk));
LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
// First, walk, count and grab all entries from the storage
// TODO
// - walk files on disk, when the storage is not private
// - should create representative entries only for the time
// of need
mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
@ -200,37 +224,42 @@ private:
CacheEntryTable* entries;
if (sGlobalEntryTables->Get(mContextKey, &entries))
entries->EnumerateRead(&WalkRunnable::WalkStorage, this);
entries->EnumerateRead(&WalkMemoryCacheRunnable::WalkStorage, this);
// Next, we dispatch to the main thread
}
else if (NS_IsMainThread()) {
LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk));
} else if (NS_IsMainThread()) {
LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
if (mNotifyStorage) {
LOG((" storage"));
// Second, notify overall storage info
mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize);
mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize,
CacheObserver::MemoryCacheCapacity(), nullptr);
if (!mVisitEntries)
return NS_OK; // done
mNotifyStorage = false;
}
else {
} else {
LOG((" entry [left=%d]", mEntryArray.Length()));
// Third, notify each entry until depleted.
// Third, notify each entry until depleted
if (!mEntryArray.Length()) {
mCallback->OnCacheEntryVisitCompleted();
return NS_OK; // done
}
mCallback->OnCacheEntryInfo(mEntryArray[0]);
// Grab the next entry
nsRefPtr<CacheEntry> entry = mEntryArray[0];
mEntryArray.RemoveElementAt(0);
// Dispatch to the main thread again
// Invokes this->OnEntryInfo, that calls the callback with all
// information of the entry.
CacheStorageService::GetCacheEntryInfo(entry, this);
}
}
else {
MOZ_ASSERT(false);
} else {
MOZ_CRASH("Bad thread");
return NS_ERROR_FAILURE;
}
@ -238,7 +267,7 @@ private:
return NS_OK;
}
virtual ~WalkRunnable()
virtual ~WalkMemoryCacheRunnable()
{
if (mCallback)
ProxyReleaseMainThread(mCallback);
@ -249,9 +278,11 @@ private:
CacheEntry* aEntry,
void* aClosure)
{
WalkRunnable* walker = static_cast<WalkRunnable*>(aClosure);
WalkMemoryCacheRunnable* walker =
static_cast<WalkMemoryCacheRunnable*>(aClosure);
if (!walker->mUsingDisk && aEntry->IsUsingDiskLocked())
// Ignore disk entries
if (aEntry->IsUsingDiskLocked())
return PL_DHASH_NEXT;
walker->mSize += aEntry->GetMetadataMemoryConsumption();
@ -264,15 +295,196 @@ private:
return PL_DHASH_NEXT;
}
virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
int64_t aDataSize, int32_t aFetchCount,
uint32_t aLastModifiedTime, uint32_t aExpirationTime)
{
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
if (NS_FAILED(rv))
return;
mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aFetchCount,
aLastModifiedTime, aExpirationTime);
}
private:
nsCString mContextKey;
nsCOMPtr<nsICacheStorageVisitor> mCallback;
nsTArray<nsRefPtr<CacheEntry> > mEntryArray;
};
uint64_t mSize;
// WalkDiskCacheRunnable
// Using the cache index information to get the list of files per context.
class WalkDiskCacheRunnable : public WalkCacheRunnable
{
public:
WalkDiskCacheRunnable(nsILoadContextInfo *aLoadInfo,
bool aVisitEntries,
nsICacheStorageVisitor* aVisitor)
: WalkCacheRunnable(aVisitor, aVisitEntries)
, mLoadInfo(aLoadInfo)
, mPass(COLLECT_STATS)
{
}
bool mNotifyStorage : 1;
bool mVisitEntries : 1;
bool mUsingDisk : 1;
nsresult Walk()
{
// TODO, bug 998693
// Initial index build should be forced here so that about:cache soon
// after startup gives some meaningfull results.
// Dispatch to the INDEX level in hope that very recent cache entries
// information gets to the index list before we grab the index iterator
// for the first time. This tries to avoid miss of entries that has
// been created right before the visit is required.
nsRefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
return thread->Dispatch(this, CacheIOThread::INDEX);
}
private:
// Invokes OnCacheEntryInfo callback for each single found entry.
// There is one instance of this class per one entry.
class OnCacheEntryInfoRunnable : public nsRunnable
{
public:
OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
: mWalker(aWalker)
{
}
NS_IMETHODIMP Run()
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
if (NS_FAILED(rv))
return NS_OK;
mWalker->mCallback->OnCacheEntryInfo(
uri, mIdEnhance, mDataSize, mFetchCount,
mLastModifiedTime, mExpirationTime);
return NS_OK;
}
nsRefPtr<WalkDiskCacheRunnable> mWalker;
nsCString mURISpec;
nsCString mIdEnhance;
int64_t mDataSize;
int32_t mFetchCount;
uint32_t mLastModifiedTime;
uint32_t mExpirationTime;
};
NS_IMETHODIMP Run()
{
// The main loop
nsresult rv;
if (CacheStorageService::IsOnManagementThread()) {
switch (mPass) {
case COLLECT_STATS:
// Get quickly the cache stats.
uint32_t size;
rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
if (NS_FAILED(rv)) {
if (mVisitEntries) {
// both onStorageInfo and onCompleted are expected
NS_DispatchToMainThread(this);
}
return NS_DispatchToMainThread(this);
}
mSize = size << 10;
// Invoke onCacheStorageInfo with valid information.
NS_DispatchToMainThread(this);
if (!mVisitEntries) {
return NS_OK; // done
}
mPass = ITERATE_METADATA;
// no break
case ITERATE_METADATA:
// Now grab the context iterator.
if (!mIter) {
rv = CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
if (NS_FAILED(rv)) {
// Invoke onCacheEntryVisitCompleted now
return NS_DispatchToMainThread(this);
}
}
while (true) {
if (CacheIOThread::YieldAndRerun())
return NS_OK;
SHA1Sum::Hash hash;
rv = mIter->GetNextHash(&hash);
if (NS_FAILED(rv))
break; // done (or error?)
// This synchronously invokes onCacheEntryInfo on this class where we
// redispatch to the main thread for the consumer callback.
CacheFileIOManager::GetEntryInfo(&hash, this);
}
// Invoke onCacheEntryVisitCompleted on the main thread
NS_DispatchToMainThread(this);
}
} else if (NS_IsMainThread()) {
if (mNotifyStorage) {
nsCOMPtr<nsIFile> dir;
CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
mCallback->OnCacheStorageInfo(mCount, mSize, CacheObserver::DiskCacheCapacity(), dir);
mNotifyStorage = false;
} else {
mCallback->OnCacheEntryVisitCompleted();
}
} else {
MOZ_CRASH("Bad thread");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
int64_t aDataSize, int32_t aFetchCount,
uint32_t aLastModifiedTime, uint32_t aExpirationTime)
{
// Called directly from CacheFileIOManager::GetEntryInfo.
// Invoke onCacheEntryInfo on the main thread for this entry.
nsRefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
info->mURISpec = aURISpec;
info->mIdEnhance = aIdEnhance;
info->mDataSize = aDataSize;
info->mFetchCount = aFetchCount;
info->mLastModifiedTime = aLastModifiedTime;
info->mExpirationTime = aExpirationTime;
NS_DispatchToMainThread(info);
}
nsRefPtr<nsILoadContextInfo> mLoadInfo;
enum {
// First, we collect stats for the load context.
COLLECT_STATS,
// Second, if demanded, we iterate over the entries gethered
// from the iterator and call CacheFileIOManager::GetEntryInfo
// for each found entry.
ITERATE_METADATA,
} mPass;
nsRefPtr<CacheIndexIterator> mIter;
uint32_t mCount;
};
PLDHashOperator CollectPrivateContexts(const nsACString& aKey,
@ -1264,12 +1476,15 @@ CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage,
NS_ENSURE_ARG(aStorage);
nsAutoCString contextKey;
CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
if (aStorage->WriteToDisk()) {
nsRefPtr<WalkDiskCacheRunnable> event =
new WalkDiskCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
return event->Walk();
}
nsRefPtr<WalkRunnable> event = new WalkRunnable(
contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor);
return Dispatch(event);
nsRefPtr<WalkMemoryCacheRunnable> event =
new WalkMemoryCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
return event->Walk();
}
void
@ -1308,6 +1523,73 @@ CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
entry->DoomAlreadyRemoved();
}
bool
CacheStorageService::GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
const nsACString & aIdExtension,
const nsACString & aURISpec,
EntryInfoCallback *aCallback)
{
nsAutoCString contextKey;
CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
nsAutoCString entryKey;
CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey);
nsRefPtr<CacheEntry> entry;
{
mozilla::MutexAutoLock lock(mLock);
if (mShutdown) {
return false;
}
CacheEntryTable* entries;
if (!sGlobalEntryTables->Get(contextKey, &entries)) {
return false;
}
if (!entries->Get(entryKey, getter_AddRefs(entry))) {
return false;
}
}
GetCacheEntryInfo(entry, aCallback);
return true;
}
// static
void
CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
EntryInfoCallback *aCallback)
{
nsIURI* uri = aEntry->GetURI();
nsAutoCString uriSpec;
if (uri) {
uri->GetAsciiSpec(uriSpec);
}
nsCString const enhanceId = aEntry->GetEnhanceID();
uint32_t dataSize;
if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
dataSize = 0;
}
int32_t fetchCount;
if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
fetchCount = 0;
}
uint32_t lastModified;
if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
lastModified = 0;
}
uint32_t expirationTime;
if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
expirationTime = 0;
}
aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize,
fetchCount, lastModified, expirationTime);
}
// nsIMemoryReporter
size_t

View File

@ -88,6 +88,19 @@ public:
already_AddRefed<nsIEventTarget> Thread() const;
mozilla::Mutex& Lock() { return mLock; }
// Helper thread-safe interface to pass entry info, only difference from
// nsICacheStorageVisitor is that instead of nsIURI only the uri spec is
// passed.
class EntryInfoCallback {
public:
virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
int64_t aDataSize, int32_t aFetchCount,
uint32_t aLastModifiedTime, uint32_t aExpirationTime) = 0;
};
// Invokes OnEntryInfo for the given aEntry, synchronously.
static void GetCacheEntryInfo(CacheEntry* aEntry, EntryInfoCallback *aVisitor);
// Memory reporting
size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
@ -179,6 +192,19 @@ private:
const nsACString & aIdExtension,
const nsACString & aURISpec);
/**
* Tries to find an existing entry in the hashtables and synchronously call
* OnCacheEntryInfo of the aVisitor callback when found.
* @retuns
* true, when the entry has been found that also implies the callbacks has
* beem invoked
* false, when an entry has not been found
*/
bool GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
const nsACString & aIdExtension,
const nsACString & aURISpec,
EntryInfoCallback *aCallback);
private:
friend class CacheMemoryConsumer;

View File

@ -5,9 +5,9 @@
#include "CacheStorage.h"
#include "CacheStorageService.h"
#include "LoadContextInfo.h"
#include "nsCacheService.h"
#include "nsIURI.h"
#include "nsICacheService.h"
#include "nsICacheSession.h"
#include "nsIApplicationCache.h"
#include "nsIApplicationCacheService.h"
@ -19,6 +19,7 @@
#include "nsServiceManagerUtils.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "mozilla/Telemetry.h"
@ -124,43 +125,31 @@ NS_IMETHODIMP DoomCallbackWrapper::OnCacheEntryDoomed(nsresult status)
return NS_OK;
}
} // anon
// _OldVisitCallbackWrapper
// Receives visit callbacks from the old API and forwards it to the new API
class VisitCallbackWrapper : public nsICacheVisitor
{
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHEVISITOR
NS_IMPL_ISUPPORTS(_OldVisitCallbackWrapper, nsICacheVisitor)
VisitCallbackWrapper(char* const deviceID,
nsICacheStorageVisitor* cb,
bool visitEntries)
: mCB(cb)
, mVisitEntries(visitEntries)
, mDeviceID(deviceID)
{
MOZ_COUNT_CTOR(VisitCallbackWrapper);
_OldVisitCallbackWrapper::~_OldVisitCallbackWrapper()
{
if (!mHit) {
// The device has not been found, to not break the chain, simulate
// storage info callback.
mCB->OnCacheStorageInfo(0, 0, 0, nullptr);
}
private:
virtual ~VisitCallbackWrapper();
nsCOMPtr<nsICacheStorageVisitor> mCB;
bool mVisitEntries;
char* const mDeviceID;
};
NS_IMPL_ISUPPORTS(VisitCallbackWrapper, nsICacheVisitor)
VisitCallbackWrapper::~VisitCallbackWrapper()
{
if (mVisitEntries)
if (mVisitEntries) {
mCB->OnCacheEntryVisitCompleted();
}
MOZ_COUNT_DTOR(VisitCallbackWrapper);
MOZ_COUNT_DTOR(_OldVisitCallbackWrapper);
}
NS_IMETHODIMP VisitCallbackWrapper::VisitDevice(const char * deviceID,
nsICacheDeviceInfo *deviceInfo,
bool *_retval)
NS_IMETHODIMP _OldVisitCallbackWrapper::VisitDevice(const char * deviceID,
nsICacheDeviceInfo *deviceInfo,
bool *_retval)
{
if (!mCB)
return NS_ERROR_NULL_POINTER;
@ -171,38 +160,120 @@ NS_IMETHODIMP VisitCallbackWrapper::VisitDevice(const char * deviceID,
return NS_OK;
}
mHit = true;
nsresult rv;
uint32_t entryCount;
rv = deviceInfo->GetEntryCount(&entryCount);
uint32_t capacity;
rv = deviceInfo->GetMaximumSize(&capacity);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t totalSize;
rv = deviceInfo->GetTotalSize(&totalSize);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> dir;
if (!strcmp(mDeviceID, "disk")) {
nsCacheService::GetDiskCacheDirectory(getter_AddRefs(dir));
} else if (!strcmp(mDeviceID, "offline")) {
nsCacheService::GetAppCacheDirectory(getter_AddRefs(dir));
}
if (mLoadInfo->IsAnonymous()) {
// Anonymous visiting reports 0, 0 since we cannot count that
// early the number of anon entries.
mCB->OnCacheStorageInfo(0, 0, capacity, dir);
} else {
// Non-anon visitor counts all non-anon + ALL ANON entries,
// there is no way to determine the number of entries when
// using the old cache APIs - there is no concept of anonymous
// storage.
uint32_t entryCount;
rv = deviceInfo->GetEntryCount(&entryCount);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t totalSize;
rv = deviceInfo->GetTotalSize(&totalSize);
NS_ENSURE_SUCCESS(rv, rv);
mCB->OnCacheStorageInfo(entryCount, totalSize, capacity, dir);
}
mCB->OnCacheStorageInfo(entryCount, totalSize);
*_retval = mVisitEntries;
return NS_OK;
}
NS_IMETHODIMP VisitCallbackWrapper::VisitEntry(const char * deviceID,
nsICacheEntryInfo *entryInfo,
bool *_retval)
NS_IMETHODIMP _OldVisitCallbackWrapper::VisitEntry(const char * deviceID,
nsICacheEntryInfo *entryInfo,
bool *_retval)
{
MOZ_ASSERT(!strcmp(deviceID, mDeviceID));
nsRefPtr<_OldCacheEntryWrapper> wrapper = new _OldCacheEntryWrapper(entryInfo);
nsresult rv = mCB->OnCacheEntryInfo(wrapper);
*_retval = NS_SUCCEEDED(rv);
nsresult rv;
*_retval = true;
// Read all informative properties from the entry.
nsXPIDLCString clientId;
rv = entryInfo->GetClientID(getter_Copies(clientId));
if (NS_FAILED(rv))
return NS_OK;
if (mLoadInfo->IsPrivate() !=
StringBeginsWith(clientId, NS_LITERAL_CSTRING("HTTP-memory-only-PB"))) {
return NS_OK;
}
nsAutoCString cacheKey, enhanceId;
rv = entryInfo->GetKey(cacheKey);
if (NS_FAILED(rv))
return NS_OK;
if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("anon&"))) {
if (!mLoadInfo->IsAnonymous())
return NS_OK;
cacheKey = Substring(cacheKey, 5, cacheKey.Length());
} else if (mLoadInfo->IsAnonymous()) {
return NS_OK;
}
if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("id="))) {
int32_t uriSpecEnd = cacheKey.Find("&uri=");
if (uriSpecEnd == kNotFound) // Corrupted, ignore
return NS_OK;
enhanceId = Substring(cacheKey, 3, uriSpecEnd - 3);
cacheKey = Substring(cacheKey, uriSpecEnd + 1, cacheKey.Length());
}
if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("uri="))) {
cacheKey = Substring(cacheKey, 4, cacheKey.Length());
}
nsCOMPtr<nsIURI> uri;
// cacheKey is strip of any prefixes
rv = NS_NewURI(getter_AddRefs(uri), cacheKey);
if (NS_FAILED(rv))
return NS_OK;
uint32_t dataSize;
if (NS_FAILED(entryInfo->GetDataSize(&dataSize)))
dataSize = 0;
int32_t fetchCount;
if (NS_FAILED(entryInfo->GetFetchCount(&fetchCount)))
fetchCount = 0;
uint32_t expirationTime;
if (NS_FAILED(entryInfo->GetExpirationTime(&expirationTime)))
expirationTime = 0;
uint32_t lastModified;
if (NS_FAILED(entryInfo->GetLastModified(&lastModified)))
lastModified = 0;
// Send them to the consumer.
rv = mCB->OnCacheEntryInfo(
uri, enhanceId, (int64_t)dataSize, fetchCount, lastModified, expirationTime);
*_retval = NS_SUCCEEDED(rv);
return NS_OK;
}
} // anon
// _OldGetDiskConsumption
//static
@ -407,6 +478,35 @@ NS_IMETHODIMP _OldCacheEntryWrapper::HasWriteAccess(bool aWriteAllowed_unused, b
return NS_OK;
}
namespace { // anon
class MetaDataVisitorWrapper : public nsICacheMetaDataVisitor
{
NS_DECL_ISUPPORTS
NS_DECL_NSICACHEMETADATAVISITOR
MetaDataVisitorWrapper(nsICacheEntryMetaDataVisitor* cb) : mCB(cb) {}
virtual ~MetaDataVisitorWrapper() {}
nsCOMPtr<nsICacheEntryMetaDataVisitor> mCB;
};
NS_IMPL_ISUPPORTS(MetaDataVisitorWrapper, nsICacheMetaDataVisitor)
NS_IMETHODIMP
MetaDataVisitorWrapper::VisitMetaDataElement(char const * key,
char const * value,
bool *goon)
{
*goon = true;
return mCB->OnMetaDataElement(key, value);
}
} // anon
NS_IMETHODIMP _OldCacheEntryWrapper::VisitMetaData(nsICacheEntryMetaDataVisitor* cb)
{
nsRefPtr<MetaDataVisitorWrapper> w = new MetaDataVisitorWrapper(cb);
return mOldDesc->VisitMetaData(w);
}
namespace { // anon
@ -956,23 +1056,10 @@ NS_IMETHODIMP _OldStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
NS_ENSURE_ARG(aVisitor);
if (mLoadInfo->IsAnonymous()) {
// There is no concept of 'anonymous' storage in the old cache
// since anon cache entries are stored in 'non-anon' storage
// with a special prefix.
// Just fake we have 0 items with 0 consumption. This at least
// prevents displaying double size in the advanced section of
// the Options dialog.
aVisitor->OnCacheStorageInfo(0, 0);
if (aVisitEntries)
aVisitor->OnCacheEntryVisitCompleted();
return NS_OK;
}
nsresult rv;
nsCOMPtr<nsICacheService> serv =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
char* deviceID;
@ -984,8 +1071,8 @@ NS_IMETHODIMP _OldStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
deviceID = const_cast<char*>("disk");
}
nsRefPtr<VisitCallbackWrapper> cb = new VisitCallbackWrapper(
deviceID, aVisitor, aVisitEntries);
nsRefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
deviceID, aVisitor, aVisitEntries, mLoadInfo);
rv = serv->VisitEntries(cb);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -10,6 +10,7 @@
#include "nsCOMPtr.h"
#include "nsICacheEntryOpenCallback.h"
#include "nsICacheEntryDescriptor.h"
#include "nsICacheStorageVisitor.h"
#include "nsThreadUtils.h"
#include "mozilla/TimeStamp.h"
@ -40,6 +41,7 @@ public:
NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval);
NS_IMETHOD MaybeMarkValid();
NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess);
NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor*);
_OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
_OldCacheEntryWrapper(nsICacheEntryInfo* info);
@ -122,6 +124,33 @@ private:
bool const mOfflineStorage : 1;
};
class _OldVisitCallbackWrapper : public nsICacheVisitor
{
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICACHEVISITOR
_OldVisitCallbackWrapper(char const * deviceID,
nsICacheStorageVisitor * cb,
bool visitEntries,
nsILoadContextInfo * aInfo)
: mCB(cb)
, mVisitEntries(visitEntries)
, mDeviceID(deviceID)
, mLoadInfo(aInfo)
, mHit(false)
{
MOZ_COUNT_CTOR(_OldVisitCallbackWrapper);
}
private:
virtual ~_OldVisitCallbackWrapper();
nsCOMPtr<nsICacheStorageVisitor> mCB;
bool mVisitEntries;
char const * mDeviceID;
nsCOMPtr<nsILoadContextInfo> mLoadInfo;
bool mHit; // set to true when the device was found
};
class _OldGetDiskConsumption : public nsRunnable,
public nsICacheVisitor
{

View File

@ -14,9 +14,9 @@ typedef long nsCacheStoragePolicy;
interface nsICacheListener;
interface nsIFile;
interface nsICacheMetaDataVisitor;
interface nsICacheEntryMetaDataVisitor;
[scriptable, uuid(3058bf1e-5116-41cf-826b-e6981308d414)]
[scriptable, uuid(972dc51d-df01-4b1e-b7f3-76dbcc603b1e)]
interface nsICacheEntry : nsISupports
{
/**
@ -124,6 +124,16 @@ interface nsICacheEntry : nsISupports
string getMetaDataElement(in string key);
void setMetaDataElement(in string key, in string value);
/**
* Obtain the list of metadata keys this entry keeps.
*
* NOTE: The callback is invoked under the CacheFile's lock. It means
* there should not be made any calls to the entry from the visitor and
* if the values need to be processed somehow, it's better to cache them
* and process outside the callback.
*/
void visitMetaData(in nsICacheEntryMetaDataVisitor visitor);
/**
* Claims that all metadata on this entry are up-to-date and this entry
* now can be delivered to other waiting consumers.
@ -207,3 +217,16 @@ interface nsICacheEntry : nsISupports
*/
boolean hasWriteAccess(in boolean aWriteAllowed);
};
/**
* Argument for nsICacheEntry.visitMetaData, provides access to all metadata
* keys and values stored on the entry.
*/
[scriptable, uuid(fea3e276-6ba5-4ceb-a581-807d1f43f6d0)]
interface nsICacheEntryMetaDataVisitor : nsISupports
{
/**
* Called over each key / value pair.
*/
void onMetaDataElement(in string key, in string value);
};

View File

@ -4,18 +4,27 @@
#include "nsISupports.idl"
interface nsICacheEntry;
interface nsIURI;
interface nsIFile;
[scriptable, uuid(692dda47-3b21-4d0d-853a-f4d27cc324d0)]
[scriptable, uuid(946bd799-9410-4945-9085-79c7fe019e83)]
interface nsICacheStorageVisitor : nsISupports
{
/**
*/
void onCacheStorageInfo(in uint32_t aEntryCount, in uint64_t aConsumption);
void onCacheStorageInfo(in uint32_t aEntryCount,
in uint64_t aConsumption,
in uint64_t aCapacity,
in nsIFile aDiskDirectory);
/**
*/
void onCacheEntryInfo(in nsICacheEntry aEntry);
void onCacheEntryInfo(in nsIURI aURI,
in ACString aIdEnhance,
in int64_t aDataSize,
in long aFetchCount,
in uint32_t aLastModifiedTime,
in uint32_t aExpirationTime);
/**
*/

View File

@ -30,4 +30,5 @@ FINAL_LIBRARY = 'necko'
LOCAL_INCLUDES += [
'../../base/src',
'../../cache2',
]

View File

@ -11,275 +11,423 @@
#include "nsNetUtil.h"
#include "nsEscape.h"
#include "nsAboutProtocolUtils.h"
#include "nsPrintfCString.h"
#include "nsICacheService.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "CacheFileUtils.h"
#include "CacheObserver.h"
NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule, nsICacheVisitor)
#include "nsThreadUtils.h"
using namespace mozilla::net;
NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule, nsICacheStorageVisitor)
NS_IMETHODIMP
nsAboutCache::NewChannel(nsIURI *aURI, nsIChannel **result)
{
NS_ENSURE_ARG_POINTER(aURI);
nsresult rv;
uint32_t bytesWritten;
*result = nullptr;
// Get the cache manager service
nsCOMPtr<nsICacheService> cacheService =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream),
16384, (uint32_t)-1,
true, // non-blocking input
false // blocking output
);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStorageStream> storageStream;
nsCOMPtr<nsIOutputStream> outputStream;
// Init: (block size, maximum length)
rv = NS_NewStorageStream(256, (uint32_t)-1, getter_AddRefs(storageStream));
nsAutoCString storageName;
rv = ParseURI(aURI, storageName);
if (NS_FAILED(rv)) return rv;
rv = storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
if (NS_FAILED(rv)) return rv;
mBuffer.AssignLiteral(
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Information about the Cache Service</title>\n"
" <link rel=\"stylesheet\" "
"href=\"chrome://global/skin/about.css\" type=\"text/css\"/>\n"
" <link rel=\"stylesheet\" "
"href=\"chrome://global/skin/aboutCache.css\" type=\"text/css\"/>\n"
"</head>\n"
"<body class=\"aboutPageWideContainer\">\n"
"<h1>Information about the Cache Service</h1>\n");
outputStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
rv = ParseURI(aURI, mDeviceID);
if (NS_FAILED(rv)) return rv;
mStream = outputStream;
// nsCacheService::VisitEntries calls nsMemoryCacheDevice::Visit,
// nsDiskCacheDevice::Visit and nsOfflineCacheDevice::Visit,
// each of which call
// 1. VisitDevice (for about:cache),
// 2. VisitEntry in a loop (for about:cache?device=disk etc.)
rv = cacheService->VisitEntries(this);
mBuffer.Truncate();
if (rv == NS_ERROR_NOT_AVAILABLE) {
mBuffer.AppendLiteral("<h2>The cache is disabled.</h2>\n");
}
else if (NS_FAILED(rv)) {
return rv;
mOverview = storageName.IsEmpty();
if (mOverview) {
// ...and visit all we can
mStorageList.AppendElement(NS_LITERAL_CSTRING("memory"));
mStorageList.AppendElement(NS_LITERAL_CSTRING("disk"));
mStorageList.AppendElement(NS_LITERAL_CSTRING("appcache"));
} else {
// ...and visit just the specified storage, entries will output too
mStorageList.AppendElement(storageName);
}
if (!mDeviceID.IsEmpty()) {
mBuffer.AppendLiteral("</table>\n");
}
mBuffer.AppendLiteral("</body>\n"
"</html>\n");
outputStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
nsCOMPtr<nsIInputStream> inStr;
rv = storageStream->NewInputStream(0, getter_AddRefs(inStr));
if (NS_FAILED(rv)) return rv;
// The entries header is added on encounter of the first entry
mEntriesHeaderAdded = false;
nsCOMPtr<nsIChannel> channel;
rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aURI, inStr,
rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aURI, inputStream,
NS_LITERAL_CSTRING("text/html"),
NS_LITERAL_CSTRING("utf-8"));
if (NS_FAILED(rv)) return rv;
mBuffer.AssignLiteral(
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <title>Network Cache Storage Information</title>\n"
" <meta charset=\"utf-8\">\n"
" <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
" <link rel=\"stylesheet\" href=\"chrome://global/skin/aboutCache.css\"/>\n"
" <script src=\"chrome://global/content/aboutCache.js\"></script>"
"</head>\n"
"<body class=\"aboutPageWideContainer\">\n"
"<h1>Information about the Network Cache Storage Service</h1>\n");
// Add the context switch controls
mBuffer.AppendLiteral(
"<label><input id='priv' type='checkbox'/> Private</label>\n"
"<label><input id='anon' type='checkbox'/> Anonymous</label>\n"
);
if (CacheObserver::UseNewCache()) {
// Visit scoping by browser and appid is not implemented for
// the old cache, simply don't add these controls.
// The appid/inbrowser entries are already mixed in the default
// view anyway.
mBuffer.AppendLiteral(
"<label><input id='appid' type='text' size='6'/> AppID</label>\n"
"<label><input id='inbrowser' type='checkbox'/> In Browser Element</label>\n"
);
}
mBuffer.AppendLiteral(
"<label><input id='submit' type='button' value='Update' onclick='navigate()'/></label>\n"
);
if (!mOverview) {
mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
char* escapedContext = nsEscapeHTML(mContextString.get());
mBuffer.Append(escapedContext);
nsMemory::Free(escapedContext);
mBuffer.AppendLiteral("\">Back to overview</a>");
}
FlushBuffer();
// Kick it, this goes async.
rv = VisitNextStorage();
if (NS_FAILED(rv)) return rv;
channel.forget(result);
return rv;
return NS_OK;
}
NS_IMETHODIMP
nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
nsresult
nsAboutCache::ParseURI(nsIURI * uri, nsACString & storage)
{
*result = 0;
//
// about:cache[?storage=<storage-name>[&context=<context-key>]]
//
nsresult rv;
nsAutoCString path;
rv = uri->GetPath(path);
if (NS_FAILED(rv)) return rv;
mContextString.Truncate();
mLoadInfo = CacheFileUtils::ParseKey(NS_LITERAL_CSTRING(""));
storage.Truncate();
nsACString::const_iterator start, valueStart, end;
path.BeginReading(start);
path.EndReading(end);
valueStart = end;
if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), start, valueStart)) {
return NS_OK;
}
nsACString::const_iterator storageNameBegin = valueStart;
start = valueStart;
valueStart = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&context="), start, valueStart))
start = end;
nsACString::const_iterator storageNameEnd = start;
mContextString = Substring(valueStart, end);
mLoadInfo = CacheFileUtils::ParseKey(mContextString);
storage.Assign(Substring(storageNameBegin, storageNameEnd));
return NS_OK;
}
nsresult
nsAboutCache::VisitNextStorage()
{
if (!mStorageList.Length())
return NS_ERROR_NOT_AVAILABLE;
mStorageName = mStorageList[0];
mStorageList.RemoveElementAt(0);
// Must re-dispatch since we cannot start another visit cycle
// from visitor callback. The cache v1 service doesn't like it.
// TODO - mayhemer, bug 913828, remove this dispatch and call
// directly.
nsCOMPtr<nsRunnableMethod<nsAboutCache> > event =
NS_NewRunnableMethod(this, &nsAboutCache::FireVisitStorage);
return NS_DispatchToMainThread(event);
}
void
nsAboutCache::FireVisitStorage()
{
nsresult rv;
rv = VisitStorage(mStorageName);
if (NS_FAILED(rv)) {
if (mLoadInfo) {
mBuffer.Append(
nsPrintfCString("<p>Unrecognized storage name '%s' in about:cache URL</p>",
mStorageName.get()));
} else {
mBuffer.Append(
nsPrintfCString("<p>Unrecognized context key '%s' in about:cache URL</p>",
mContextString.get()));
}
FlushBuffer();
// Simulate finish of a visit cycle, this tries the next storage
// or closes the output stream (i.e. the UI loader will stop spinning)
OnCacheEntryVisitCompleted();
}
}
nsresult
nsAboutCache::VisitStorage(nsACString const & storageName)
{
nsresult rv;
rv = GetStorage(storageName, mLoadInfo, getter_AddRefs(mStorage));
if (NS_FAILED(rv)) return rv;
rv = mStorage->AsyncVisitStorage(this, !mOverview);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
//static
nsresult
nsAboutCache::GetStorage(nsACString const & storageName,
nsILoadContextInfo* loadInfo,
nsICacheStorage **storage)
{
nsresult rv;
nsCOMPtr<nsICacheStorageService> cacheService =
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsICacheStorage> cacheStorage;
if (storageName == "disk") {
rv = cacheService->DiskCacheStorage(
loadInfo, false, getter_AddRefs(cacheStorage));
} else if (storageName == "memory") {
rv = cacheService->MemoryCacheStorage(
loadInfo, getter_AddRefs(cacheStorage));
} else if (storageName == "appcache") {
rv = cacheService->AppCacheStorage(
loadInfo, nullptr, getter_AddRefs(cacheStorage));
} else {
rv = NS_ERROR_UNEXPECTED;
}
if (NS_FAILED(rv)) return rv;
cacheStorage.forget(storage);
return NS_OK;
}
NS_IMETHODIMP
nsAboutCache::VisitDevice(const char *deviceID,
nsICacheDeviceInfo *deviceInfo,
bool *visitEntries)
nsAboutCache::OnCacheStorageInfo(uint32_t aEntryCount, uint64_t aConsumption,
uint64_t aCapacity, nsIFile * aDirectory)
{
uint32_t bytesWritten, value, entryCount;
nsXPIDLCString str;
// We need mStream for this
if (!mStream) {
return NS_ERROR_FAILURE;
}
*visitEntries = false;
mBuffer.AssignLiteral("<h2>");
mBuffer.Append(mStorageName);
mBuffer.AppendLiteral("</h2>\n"
"<table id=\"");
mBuffer.AppendLiteral("\">\n");
if (mDeviceID.IsEmpty() || mDeviceID.Equals(deviceID)) {
// Write out cache info
// Number of entries
mBuffer.AppendLiteral(" <tr>\n"
" <th>Number of entries:</th>\n"
" <td>");
mBuffer.AppendInt(aEntryCount);
mBuffer.AppendLiteral("</td>\n"
" </tr>\n");
// We need mStream for this
if (!mStream)
return NS_ERROR_FAILURE;
// Write out the Cache Name
deviceInfo->GetDescription(getter_Copies(str));
// Maximum storage size
mBuffer.AppendLiteral(" <tr>\n"
" <th>Maximum storage size:</th>\n"
" <td>");
mBuffer.AppendInt(aCapacity / 1024);
mBuffer.AppendLiteral(" KiB</td>\n"
" </tr>\n");
mBuffer.AssignLiteral("<h2>");
mBuffer.Append(str);
mBuffer.AppendLiteral("</h2>\n"
"<table id=\"");
mBuffer.Append(deviceID);
mBuffer.AppendLiteral("\">\n");
// Storage in use
mBuffer.AppendLiteral(" <tr>\n"
" <th>Storage in use:</th>\n"
" <td>");
mBuffer.AppendInt(aConsumption / 1024);
mBuffer.AppendLiteral(" KiB</td>\n"
" </tr>\n");
// Write out cache info
// Number of entries
mBuffer.AppendLiteral(" <tr>\n"
" <th>Number of entries:</th>\n"
" <td>");
entryCount = 0;
deviceInfo->GetEntryCount(&entryCount);
mBuffer.AppendInt(entryCount);
mBuffer.AppendLiteral("</td>\n"
" </tr>\n");
// Storage disk location
mBuffer.AppendLiteral(" <tr>\n"
" <th>Storage disk location:</th>\n"
" <td>");
if (aDirectory) {
nsAutoString path;
aDirectory->GetPath(path);
mBuffer.Append(NS_ConvertUTF16toUTF8(path));
} else {
mBuffer.AppendLiteral("none, only stored in memory");
}
mBuffer.AppendLiteral(" </td>\n"
" </tr>\n");
// Maximum storage size
mBuffer.AppendLiteral(" <tr>\n"
" <th>Maximum storage size:</th>\n"
" <td>");
value = 0;
deviceInfo->GetMaximumSize(&value);
mBuffer.AppendInt(value/1024);
mBuffer.AppendLiteral(" KiB</td>\n"
" </tr>\n");
// Storage in use
mBuffer.AppendLiteral(" <tr>\n"
" <th>Storage in use:</th>\n"
" <td>");
value = 0;
deviceInfo->GetTotalSize(&value);
mBuffer.AppendInt(value/1024);
mBuffer.AppendLiteral(" KiB</td>\n"
" </tr>\n");
deviceInfo->GetUsageReport(getter_Copies(str));
mBuffer.Append(str);
if (mDeviceID.IsEmpty()) { // The about:cache case
if (entryCount != 0) { // Add the "List Cache Entries" link
mBuffer.AppendLiteral(" <tr>\n"
" <th><a href=\"about:cache?device=");
mBuffer.Append(deviceID);
mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
" </tr>\n");
}
mBuffer.AppendLiteral("</table>\n");
} else { // The about:cache?device=disk etc. case
mBuffer.AppendLiteral("</table>\n");
if (entryCount != 0) {
*visitEntries = true;
mBuffer.AppendLiteral("<hr/>\n"
"<table id=\"entries\">\n"
" <colgroup>\n"
" <col id=\"col-key\">\n"
" <col id=\"col-dataSize\">\n"
" <col id=\"col-fetchCount\">\n"
" <col id=\"col-lastModified\">\n"
" <col id=\"col-expires\">\n"
" </colgroup>\n"
" <thead>\n"
" <tr>\n"
" <th>Key</th>\n"
" <th>Data size</th>\n"
" <th>Fetch count</th>\n"
" <th>Last modified</th>\n"
" <th>Expires</th>\n"
" </tr>\n"
" </thead>\n");
}
if (mOverview) { // The about:cache case
if (aEntryCount != 0) { // Add the "List Cache Entries" link
mBuffer.AppendLiteral(" <tr>\n"
" <th><a href=\"about:cache?storage=");
mBuffer.Append(mStorageName);
mBuffer.AppendLiteral("&amp;context=");
char* escapedContext = nsEscapeHTML(mContextString.get());
mBuffer.Append(escapedContext);
nsMemory::Free(escapedContext);
mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
" </tr>\n");
}
}
mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
mBuffer.AppendLiteral("</table>\n");
// The entries header is added on encounter of the first entry
mEntriesHeaderAdded = false;
FlushBuffer();
if (mOverview) {
// OnCacheEntryVisitCompleted() is not called when we do not iterate
// cache entries. Since this moves forward to the next storage in
// the list we want to visit, artificially call it here.
OnCacheEntryVisitCompleted();
}
return NS_OK;
}
NS_IMETHODIMP
nsAboutCache::VisitEntry(const char *deviceID,
nsICacheEntryInfo *entryInfo,
bool *visitNext)
nsAboutCache::OnCacheEntryInfo(nsIURI *aURI, const nsACString & aIdEnhance,
int64_t aDataSize, int32_t aFetchCount,
uint32_t aLastModified, uint32_t aExpirationTime)
{
// We need mStream for this
if (!mStream)
return NS_ERROR_FAILURE;
if (!mStream) {
return NS_ERROR_FAILURE;
}
nsresult rv;
uint32_t bytesWritten;
nsAutoCString key;
nsXPIDLCString clientID;
bool streamBased;
rv = entryInfo->GetKey(key);
if (NS_FAILED(rv)) return rv;
rv = entryInfo->GetClientID(getter_Copies(clientID));
if (NS_FAILED(rv)) return rv;
rv = entryInfo->IsStreamBased(&streamBased);
if (NS_FAILED(rv)) return rv;
if (!mEntriesHeaderAdded) {
mBuffer.AppendLiteral("<hr/>\n"
"<table id=\"entries\">\n"
" <colgroup>\n"
" <col id=\"col-key\">\n"
" <col id=\"col-dataSize\">\n"
" <col id=\"col-fetchCount\">\n"
" <col id=\"col-lastModified\">\n"
" <col id=\"col-expires\">\n"
" </colgroup>\n"
" <thead>\n"
" <tr>\n"
" <th>Key</th>\n"
" <th>Data size</th>\n"
" <th>Fetch count</th>\n"
" <th>Last Modifed</th>\n"
" <th>Expires</th>\n"
" </tr>\n"
" </thead>\n");
mEntriesHeaderAdded = true;
}
// Generate a about:cache-entry URL for this entry...
nsAutoCString url;
url.AssignLiteral("about:cache-entry?client=");
url += clientID;
url.AppendLiteral("&amp;sb=");
url += streamBased ? '1' : '0';
url.AppendLiteral("&amp;key=");
char* escapedKey = nsEscapeHTML(key.get());
url += escapedKey; // key
url.AssignLiteral("about:cache-entry?storage=");
url.Append(mStorageName);
url.AppendLiteral("&amp;context=");
char* escapedContext = nsEscapeHTML(mContextString.get());
url += escapedContext;
nsMemory::Free(escapedContext);
url.AppendLiteral("&amp;eid=");
char* escapedEID = nsEscapeHTML(aIdEnhance.BeginReading());
url += escapedEID;
nsMemory::Free(escapedEID);
nsAutoCString cacheUriSpec;
aURI->GetAsciiSpec(cacheUriSpec);
char* escapedCacheURI = nsEscapeHTML(cacheUriSpec.get());
url.AppendLiteral("&amp;uri=");
url += escapedCacheURI;
// Entry start...
mBuffer.AssignLiteral(" <tr>\n");
mBuffer.AppendLiteral(" <tr>\n");
// URI
mBuffer.AppendLiteral(" <td><a href=\"");
mBuffer.Append(url);
mBuffer.AppendLiteral("\">");
mBuffer.Append(escapedKey);
nsMemory::Free(escapedKey);
if (!aIdEnhance.IsEmpty()) {
mBuffer.Append(aIdEnhance);
mBuffer.Append(':');
}
mBuffer.Append(escapedCacheURI);
mBuffer.AppendLiteral("</a></td>\n");
nsMemory::Free(escapedCacheURI);
// Content length
uint32_t length = 0;
entryInfo->GetDataSize(&length);
mBuffer.AppendLiteral(" <td>");
mBuffer.AppendInt(length);
mBuffer.AppendInt(aDataSize);
mBuffer.AppendLiteral(" bytes</td>\n");
// Number of accesses
int32_t fetchCount = 0;
entryInfo->GetFetchCount(&fetchCount);
mBuffer.AppendLiteral(" <td>");
mBuffer.AppendInt(fetchCount);
mBuffer.AppendInt(aFetchCount);
mBuffer.AppendLiteral("</td>\n");
// vars for reporting time
char buf[255];
uint32_t t;
// Last modified time
mBuffer.AppendLiteral(" <td>");
entryInfo->GetLastModified(&t);
if (t) {
PrintTimeString(buf, sizeof(buf), t);
if (aLastModified) {
PrintTimeString(buf, sizeof(buf), aLastModified);
mBuffer.Append(buf);
} else
mBuffer.AppendLiteral("No last modified time");
} else {
mBuffer.AppendLiteral("No last modified time (bug 1000338)");
}
mBuffer.AppendLiteral("</td>\n");
// Expires time
mBuffer.AppendLiteral(" <td>");
entryInfo->GetExpirationTime(&t);
if (t < 0xFFFFFFFF) {
PrintTimeString(buf, sizeof(buf), t);
if (aExpirationTime < 0xFFFFFFFF) {
PrintTimeString(buf, sizeof(buf), aExpirationTime);
mBuffer.Append(buf);
} else {
mBuffer.AppendLiteral("No expiration time");
@ -289,40 +437,55 @@ nsAboutCache::VisitEntry(const char *deviceID,
// Entry is done...
mBuffer.AppendLiteral(" </tr>\n");
mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
*visitNext = true;
FlushBuffer();
return NS_OK;
}
nsresult
nsAboutCache::ParseURI(nsIURI * uri, nsCString &deviceID)
NS_IMETHODIMP
nsAboutCache::OnCacheEntryVisitCompleted()
{
//
// about:cache[?device=string]
//
nsresult rv;
if (!mStream) {
return NS_ERROR_FAILURE;
}
deviceID.Truncate();
if (mEntriesHeaderAdded) {
mBuffer.AppendLiteral("</table>\n");
}
nsAutoCString path;
rv = uri->GetPath(path);
if (NS_FAILED(rv)) return rv;
// Kick another storage visiting (from a storage that allows us.)
while (mStorageList.Length()) {
nsresult rv = VisitNextStorage();
if (NS_SUCCEEDED(rv)) {
// Expecting new round of OnCache* calls.
return NS_OK;
}
}
nsACString::const_iterator start, valueStart, end;
path.BeginReading(start);
path.EndReading(end);
// We are done!
mBuffer.AppendLiteral("</body>\n"
"</html>\n");
FlushBuffer();
mStream->Close();
valueStart = end;
if (!FindInReadable(NS_LITERAL_CSTRING("?device="), start, valueStart))
return NS_OK;
deviceID.Assign(Substring(valueStart, end));
return NS_OK;
}
void
nsAboutCache::FlushBuffer()
{
uint32_t bytesWritten;
mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
mBuffer.Truncate();
}
NS_IMETHODIMP
nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
{
*result = 0;
return NS_OK;
}
// static
nsresult
nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
{
@ -335,6 +498,4 @@ nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
return rv;
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -7,20 +7,23 @@
#define nsAboutCache_h__
#include "nsIAboutModule.h"
#include "nsICacheStorageVisitor.h"
#include "nsICacheStorage.h"
#include "nsString.h"
#include "nsIOutputStream.h"
#include "nsILoadContextInfo.h"
#include "nsICacheVisitor.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
class nsAboutCache : public nsIAboutModule
, public nsICacheVisitor
, public nsICacheStorageVisitor
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIABOUTMODULE
NS_DECL_NSICACHEVISITOR
NS_DECL_NSICACHESTORAGEVISITOR
nsAboutCache() {}
virtual ~nsAboutCache() {}
@ -28,12 +31,50 @@ public:
static nsresult
Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
protected:
nsresult ParseURI(nsIURI * uri, nsCString &deviceID);
static nsresult
GetStorage(nsACString const & storageName, nsILoadContextInfo* loadInfo,
nsICacheStorage **storage);
nsCOMPtr<nsIOutputStream> mStream;
nsCString mDeviceID;
protected:
nsresult ParseURI(nsIURI * uri, nsACString & storage);
// Finds a next storage we wish to visit (we use this method
// even there is a specified storage name, which is the only
// one in the list then.) Posts FireVisitStorage() when found.
nsresult VisitNextStorage();
// Helper method that calls VisitStorage() for the current storage.
// When it fails, OnCacheEntryVisitCompleted is simlated to close
// the output stream and thus the about:cache channel.
void FireVisitStorage();
// Kiks the visit cycle for the given storage, names can be:
// "disk", "memory", "appcache"
// Note: any newly added storage type has to be manually handled here.
nsresult VisitStorage(nsACString const & storageName);
// Writes content of mBuffer to mStream and truncates
// the buffer.
void FlushBuffer();
// Whether we are showing overview status of all available
// storages.
bool mOverview;
// Flag initially false, that indicates the entries header has
// been added to the output HTML.
bool mEntriesHeaderAdded;
// The context we are working with.
nsCOMPtr<nsILoadContextInfo> mLoadInfo;
nsCString mContextString;
// The list of all storage names we want to visit
nsTArray<nsCString> mStorageList;
nsCString mStorageName;
nsCOMPtr<nsICacheStorage> mStorage;
// Output data buffering and streaming output
nsCString mBuffer;
nsCOMPtr<nsIOutputStream> mStream;
};
#define NS_ABOUT_CACHE_MODULE_CID \

View File

@ -4,16 +4,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsAboutCacheEntry.h"
#include "nsICacheService.h"
#include "nsICacheSession.h"
#include "nsAboutCache.h"
#include "nsICacheStorage.h"
#include "CacheObserver.h"
#include "nsNetUtil.h"
#include "prprf.h"
#include "nsEscape.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsAboutProtocolUtils.h"
#include "nsInputStreamPump.h"
#include "CacheFileUtils.h"
#include <algorithm>
using namespace mozilla::net;
#define HEXDUMP_MAX_ROWS 16
static void
@ -74,7 +79,9 @@ HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result)
NS_IMPL_ISUPPORTS(nsAboutCacheEntry,
nsIAboutModule,
nsICacheMetaDataVisitor)
nsICacheEntryOpenCallback,
nsICacheEntryMetaDataVisitor,
nsIStreamListener)
//-----------------------------------------------------------------------------
// nsAboutCacheEntry::nsIAboutModule
@ -146,32 +153,158 @@ nsresult
nsAboutCacheEntry::OpenCacheEntry(nsIURI *uri)
{
nsresult rv;
nsAutoCString clientID, key;
bool streamBased = true;
rv = ParseURI(uri, clientID, streamBased, key);
rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo),
mEnhanceId, getter_AddRefs(mCacheURI));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsICacheService> serv =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
if (!CacheObserver::UseNewCache() &&
mLoadInfo->IsPrivate() &&
mStorageName == NS_LITERAL_CSTRING("disk")) {
// The cache v1 is storing all private entries in the memory-only
// cache, so it would not be found in the v1 disk cache.
mStorageName = NS_LITERAL_CSTRING("memory");
}
nsCOMPtr<nsICacheSession> session;
rv = serv->CreateSession(clientID.get(),
nsICache::STORE_ANYWHERE,
streamBased,
getter_AddRefs(session));
if (NS_FAILED(rv)) return rv;
rv = session->SetDoomEntriesIfExpired(false);
if (NS_FAILED(rv)) return rv;
return session->AsyncOpenCacheEntry(key, nsICache::ACCESS_READ, this, true);
return OpenCacheEntry();
}
nsresult
nsAboutCacheEntry::OpenCacheEntry()
{
nsresult rv;
nsCOMPtr<nsICacheStorage> storage;
rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, getter_AddRefs(storage));
if (NS_FAILED(rv)) return rv;
// Invokes OnCacheEntryAvailable()
rv = storage->AsyncOpenURI(mCacheURI, mEnhanceId,
nsICacheStorage::OPEN_READONLY, this);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
nsresult
nsAboutCacheEntry::ParseURI(nsIURI *uri,
nsACString &storageName,
nsILoadContextInfo **loadInfo,
nsCString &enahnceID,
nsIURI **cacheUri)
{
//
// about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
//
nsresult rv;
nsAutoCString path;
rv = uri->GetPath(path);
if (NS_FAILED(rv))
return rv;
nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
path.BeginReading(begin);
path.EndReading(end);
keyBegin = begin; keyEnd = end;
if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd))
return NS_ERROR_FAILURE;
valBegin = keyEnd; // the value of the storage key starts after the key
keyBegin = keyEnd; keyEnd = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&context="), keyBegin, keyEnd))
return NS_ERROR_FAILURE;
storageName.Assign(Substring(valBegin, keyBegin));
valBegin = keyEnd; // the value of the context key starts after the key
keyBegin = keyEnd; keyEnd = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&eid="), keyBegin, keyEnd))
return NS_ERROR_FAILURE;
nsAutoCString contextKey(Substring(valBegin, keyBegin));
valBegin = keyEnd; // the value of the eid key starts after the key
keyBegin = keyEnd; keyEnd = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&uri="), keyBegin, keyEnd))
return NS_ERROR_FAILURE;
enahnceID.Assign(Substring(valBegin, keyBegin));
valBegin = keyEnd; // the value of the uri key starts after the key
nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
// Uf... parsing done, now get some objects from it...
nsCOMPtr<nsILoadContextInfo> info =
CacheFileUtils::ParseKey(contextKey);
if (!info)
return NS_ERROR_FAILURE;
info.forget(loadInfo);
rv = NS_NewURI(cacheUri, uriSpec);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
//-----------------------------------------------------------------------------
// helper methods
// nsICacheEntryOpenCallback implementation
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsAboutCacheEntry::OnCacheEntryCheck(nsICacheEntry *aEntry,
nsIApplicationCache *aApplicationCache,
uint32_t *result)
{
*result = nsICacheEntryOpenCallback::ENTRY_WANTED;
return NS_OK;
}
NS_IMETHODIMP
nsAboutCacheEntry::OnCacheEntryAvailable(nsICacheEntry *entry,
bool isNew,
nsIApplicationCache *aApplicationCache,
nsresult status)
{
nsresult rv;
mWaitingForData = false;
if (entry) {
rv = WriteCacheEntryDescription(entry);
} else if (!CacheObserver::UseNewCache() &&
!mLoadInfo->IsPrivate() &&
mStorageName == NS_LITERAL_CSTRING("memory")) {
// If we were not able to find the entry in the memory storage
// try again in the disk storage.
// This is a workaround for cache v1: when an originally disk
// cache entry is recreated as memory-only, it's clientID doesn't
// change and we cannot find it in "HTTP-memory-only" session.
// "Disk" cache storage looks at "HTTP".
mStorageName = NS_LITERAL_CSTRING("disk");
rv = OpenCacheEntry();
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
} else {
rv = WriteCacheEntryUnavailable();
}
if (NS_FAILED(rv)) return rv;
if (!mWaitingForData) {
// Data is not expected, close the output of content now.
CloseContent();
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// Print-out helper methods
//-----------------------------------------------------------------------------
#define APPEND_ROW(label, value) \
@ -187,7 +320,7 @@ nsAboutCacheEntry::OpenCacheEntry(nsIURI *uri)
PR_END_MACRO
nsresult
nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descriptor)
nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntry *entry)
{
nsresult rv;
nsCString buffer;
@ -195,7 +328,7 @@ nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descripto
nsAutoCString str;
rv = descriptor->GetKey(str);
rv = entry->GetKey(str);
if (NS_FAILED(rv)) return rv;
buffer.SetCapacity(4096);
@ -224,9 +357,9 @@ nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descripto
buffer.Append(escapedStr);
buffer.AppendLiteral("</a>");
uri = 0;
}
else
} else {
buffer.Append(escapedStr);
}
nsMemory::Free(escapedStr);
buffer.AppendLiteral("</td>\n"
" </tr>\n");
@ -239,30 +372,30 @@ nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descripto
// Fetch Count
s.Truncate();
descriptor->GetFetchCount(&i);
entry->GetFetchCount(&i);
s.AppendInt(i);
APPEND_ROW("fetch count", s);
// Last Fetched
descriptor->GetLastFetched(&u);
entry->GetLastFetched(&u);
if (u) {
PrintTimeString(timeBuf, sizeof(timeBuf), u);
APPEND_ROW("last fetched", timeBuf);
} else {
APPEND_ROW("last fetched", "No last fetch time");
APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
}
// Last Modified
descriptor->GetLastModified(&u);
entry->GetLastModified(&u);
if (u) {
PrintTimeString(timeBuf, sizeof(timeBuf), u);
APPEND_ROW("last modified", timeBuf);
} else {
APPEND_ROW("last modified", "No last modified time");
APPEND_ROW("last modified", "No last modified time (bug 1000338)");
}
// Expiration Time
descriptor->GetExpirationTime(&u);
entry->GetExpirationTime(&u);
if (u < 0xFFFFFFFF) {
PrintTimeString(timeBuf, sizeof(timeBuf), u);
APPEND_ROW("expires", timeBuf);
@ -273,29 +406,24 @@ nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descripto
// Data Size
s.Truncate();
uint32_t dataSize;
descriptor->GetStorageDataSize(&dataSize);
if (NS_FAILED(entry->GetStorageDataSize(&dataSize)))
dataSize = 0;
s.AppendInt((int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed.
s.Append(NS_LITERAL_CSTRING(" B"));
APPEND_ROW("Data size", s);
// Storage Policy
// XXX Stream Based?
// XXX Cache Device
// File on disk
nsCOMPtr<nsIFile> cacheFile;
rv = descriptor->GetFile(getter_AddRefs(cacheFile));
if (NS_SUCCEEDED(rv)) {
nsAutoString filePath;
cacheFile->GetPath(filePath);
APPEND_ROW("file on disk", NS_ConvertUTF16toUTF8(filePath));
}
else
APPEND_ROW("file on disk", "none");
// TODO - mayhemer
// Here used to be a link to the disk file (in the old cache for entries that
// did not fit any of the block files, in the new cache every time).
// I'd rather have a small set of buttons here to action on the entry:
// 1. save the content
// 2. save as a complete HTTP response (response head, headers, content)
// 3. doom the entry
// A new bug(s) should be filed here.
// Security Info
nsCOMPtr<nsISupports> securityInfo;
descriptor->GetSecurityInfo(getter_AddRefs(securityInfo));
entry->GetSecurityInfo(getter_AddRefs(securityInfo));
if (securityInfo) {
APPEND_ROW("Security", "This is a secure document.");
} else {
@ -306,47 +434,38 @@ nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descripto
buffer.AppendLiteral("</table>\n"
"<hr/>\n"
"<table>\n");
// Meta Data
// let's just look for some well known (HTTP) meta data tags, for now.
// Client ID
nsXPIDLCString str2;
descriptor->GetClientID(getter_Copies(str2));
if (!str2.IsEmpty()) APPEND_ROW("Client", str2);
mBuffer = &buffer; // make it available for VisitMetaDataElement().
// nsCacheEntryDescriptor::VisitMetaData calls
// nsCacheEntry.h VisitMetaDataElements, which returns
// nsCacheMetaData::VisitElements, which calls
// nsAboutCacheEntry::VisitMetaDataElement (below) in a loop.
descriptor->VisitMetaData(this);
mBuffer = &buffer; // make it available for OnMetaDataElement().
entry->VisitMetaData(this);
mBuffer = nullptr;
buffer.AppendLiteral("</table>\n");
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
buffer.Truncate();
// Provide a hexdump of the data
if (dataSize) { // don't draw an <hr> if the Data Size is 0.
nsCOMPtr<nsIInputStream> stream;
descriptor->OpenInputStream(0, getter_AddRefs(stream));
if (stream) {
buffer.AssignLiteral("<hr/>\n"
"<pre>");
uint32_t hexDumpState = 0;
char chunk[4096];
while(NS_SUCCEEDED(stream->Read(chunk, sizeof(chunk), &n)) &&
n > 0) {
HexDump(&hexDumpState, chunk, n, buffer);
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
buffer.Truncate();
}
buffer.AssignLiteral("</pre>\n");
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
}
if (!dataSize) {
return NS_OK;
}
nsCOMPtr<nsIInputStream> stream;
entry->OpenInputStream(0, getter_AddRefs(stream));
if (!stream) {
return NS_OK;
}
nsRefPtr<nsInputStreamPump> pump;
rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
if (NS_FAILED(rv)) {
return NS_OK; // just ignore
}
rv = pump->AsyncRead(this, nullptr);
if (NS_FAILED(rv)) {
return NS_OK; // just ignore
}
mWaitingForData = true;
return NS_OK;
}
@ -360,59 +479,12 @@ nsAboutCacheEntry::WriteCacheEntryUnavailable()
return NS_OK;
}
nsresult
nsAboutCacheEntry::ParseURI(nsIURI *uri, nsCString &clientID,
bool &streamBased, nsCString &key)
{
//
// about:cache-entry?client=[string]&sb=[boolean]&key=[string]
//
nsresult rv;
nsAutoCString path;
rv = uri->GetPath(path);
if (NS_FAILED(rv)) return rv;
nsACString::const_iterator i1, i2, i3, end;
path.BeginReading(i1);
path.EndReading(end);
i2 = end;
if (!FindInReadable(NS_LITERAL_CSTRING("?client="), i1, i2))
return NS_ERROR_FAILURE;
// i2 points to the start of clientID
i1 = i2;
i3 = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&sb="), i1, i3))
return NS_ERROR_FAILURE;
// i1 points to the end of clientID
// i3 points to the start of isStreamBased
clientID.Assign(Substring(i2, i1));
i1 = i3;
i2 = end;
if (!FindInReadable(NS_LITERAL_CSTRING("&key="), i1, i2))
return NS_ERROR_FAILURE;
// i1 points to the end of isStreamBased
// i2 points to the start of key
streamBased = FindCharInReadable('1', i3, i1);
key.Assign(Substring(i2, end));
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsICacheMetaDataVisitor implementation
// nsICacheEntryMetaDataVisitor implementation
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsAboutCacheEntry::VisitMetaDataElement(const char * key,
const char * value,
bool * keepGoing)
nsAboutCacheEntry::OnMetaDataElement(char const * key, char const * value)
{
mBuffer->AppendLiteral(" <tr>\n"
" <th>");
@ -425,38 +497,76 @@ nsAboutCacheEntry::VisitMetaDataElement(const char * key,
mBuffer->AppendLiteral("</td>\n"
" </tr>\n");
*keepGoing = true;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsICacheListener implementation
// nsIStreamListener implementation
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsAboutCacheEntry::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
nsCacheAccessMode access,
nsresult status)
nsAboutCacheEntry::OnStartRequest(nsIRequest *request, nsISupports *ctx)
{
nsresult rv;
mHexDumpState = 0;
if (entry)
rv = WriteCacheEntryDescription(entry);
else
rv = WriteCacheEntryUnavailable();
if (NS_FAILED(rv)) return rv;
NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>");
uint32_t n;
return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
}
NS_IMETHODIMP
nsAboutCacheEntry::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
nsIInputStream *aInputStream,
uint64_t aOffset,
uint32_t aCount)
{
uint32_t n;
return aInputStream->ReadSegments(
&nsAboutCacheEntry::PrintCacheData, this, aCount, &n);
}
// static
NS_METHOD
nsAboutCacheEntry::PrintCacheData(nsIInputStream *aInStream,
void *aClosure,
const char *aFromSegment,
uint32_t aToOffset,
uint32_t aCount,
uint32_t *aWriteCount)
{
nsAboutCacheEntry *a = static_cast<nsAboutCacheEntry*>(aClosure);
nsCString buffer;
HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
uint32_t n;
NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
mOutputStream->Close();
mOutputStream = nullptr;
a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
*aWriteCount = aCount;
return NS_OK;
}
NS_IMETHODIMP
nsAboutCacheEntry::OnCacheEntryDoomed(nsresult status)
nsAboutCacheEntry::OnStopRequest(nsIRequest *request, nsISupports *ctx,
nsresult result)
{
return NS_ERROR_NOT_IMPLEMENTED;
NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n");
uint32_t n;
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
CloseContent();
return NS_OK;
}
void
nsAboutCacheEntry::CloseContent()
{
NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
uint32_t n;
mOutputStream->Write(buffer.get(), buffer.Length(), &n);
mOutputStream->Close();
mOutputStream = nullptr;
}

View File

@ -7,27 +7,35 @@
#define nsAboutCacheEntry_h__
#include "nsIAboutModule.h"
#include "nsICacheListener.h"
#include "nsICacheEntryDescriptor.h"
#include "nsICacheEntryOpenCallback.h"
#include "nsICacheEntry.h"
#include "nsIStreamListener.h"
#include "nsString.h"
#include "nsCOMPtr.h"
class nsIAsyncOutputStream;
class nsIInputStream;
class nsILoadContextInfo;
class nsIURI;
class nsCString;
class nsAboutCacheEntry : public nsIAboutModule
, public nsICacheMetaDataVisitor
, public nsICacheListener
, public nsICacheEntryOpenCallback
, public nsICacheEntryMetaDataVisitor
, public nsIStreamListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIABOUTMODULE
NS_DECL_NSICACHEMETADATAVISITOR
NS_DECL_NSICACHELISTENER
NS_DECL_NSICACHEENTRYOPENCALLBACK
NS_DECL_NSICACHEENTRYMETADATAVISITOR
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
nsAboutCacheEntry()
: mBuffer(nullptr)
, mWaitingForData(false)
, mHexDumpState(0)
{}
virtual ~nsAboutCacheEntry() {}
@ -35,13 +43,31 @@ public:
private:
nsresult GetContentStream(nsIURI *, nsIInputStream **);
nsresult OpenCacheEntry(nsIURI *);
nsresult WriteCacheEntryDescription(nsICacheEntryDescriptor *);
nsresult OpenCacheEntry();
nsresult WriteCacheEntryDescription(nsICacheEntry *);
nsresult WriteCacheEntryUnavailable();
nsresult ParseURI(nsIURI *, nsCString &, bool &, nsCString &);
nsresult ParseURI(nsIURI *uri, nsACString &storageName,
nsILoadContextInfo **loadInfo,
nsCString &enahnceID, nsIURI **cacheUri);
void CloseContent();
static NS_METHOD
PrintCacheData(nsIInputStream *aInStream,
void *aClosure,
const char *aFromSegment,
uint32_t aToOffset,
uint32_t aCount,
uint32_t *aWriteCount);
private:
nsAutoCString mStorageName, mEnhanceId;
nsCOMPtr<nsILoadContextInfo> mLoadInfo;
nsCOMPtr<nsIURI> mCacheURI;
nsCString *mBuffer;
nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
bool mWaitingForData;
uint32_t mHexDumpState;
};
#define NS_ABOUT_CACHE_ENTRY_MODULE_CID \

View File

@ -289,9 +289,9 @@ VisitCallback.prototype =
if (!this.entries)
this.notify();
},
onCacheEntryInfo: function(entry)
onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime)
{
var key = entry.key;
var key = (aIdEnhance ? (aIdEnhance + ":") : "") + aURI.asciiSpec;
LOG_C2(this, "onCacheEntryInfo: key=" + key);
do_check_true(!!this.entries);

View File

@ -5,12 +5,17 @@ function run_test()
var storage = getCacheStorage("disk");
var mc = new MultipleCallbacks(4, function() {
syncWithCacheIOThread(function() {
var expectedConsumption = newCacheBackEndUsed()
? 4096
: 48;
storage.asyncVisitStorage(
// Test should store 4 entries
new VisitCallback(4, 48, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
new VisitCallback(4, expectedConsumption, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
storage.asyncVisitStorage(
// Still 4 entries expected, now don't walk them
new VisitCallback(4, 48, null, function() {
new VisitCallback(4, expectedConsumption, null, function() {
finish_cache2_test();
}),
false

View File

@ -16,8 +16,8 @@ function run_test()
new VisitCallback(1, 12, ["http://mem1/"], function() {
storage = getCacheStorage("disk");
storage.asyncVisitStorage(
// Previous tests should store 4 disk entries + 1 memory entry
new VisitCallback(5, 60, ["http://a/", "http://b/", "http://c/", "http://d/", "http://mem1/"], function() {
// Previous tests should store 4 disk entries
new VisitCallback(4, 4096, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
finish_cache2_test();
}),
true

View File

@ -9,8 +9,13 @@ function run_test()
storage.asyncVisitStorage(
new VisitCallback(0, 0, [], function() {
var storage = getCacheStorage("disk");
var expectedConsumption = newCacheBackEndUsed()
? 2048
: 24;
storage.asyncVisitStorage(
new VisitCallback(2, 24, ["http://a/", "http://b/"], function() {
new VisitCallback(2, expectedConsumption, ["http://a/", "http://b/"], function() {
finish_cache2_test();
}),
true

View File

@ -8,10 +8,10 @@ function run_test()
{
var storage = getCacheStorage("disk", LoadContextInfo.default);
storage.asyncVisitStorage(
new VisitCallback(1, 12, ["http://an2/"], function() {
new VisitCallback(1, 1024, ["http://an2/"], function() {
storage = getCacheStorage("disk", LoadContextInfo.anonymous);
storage.asyncVisitStorage(
new VisitCallback(1, 12, ["http://an2/"], function() {
new VisitCallback(1, 1024, ["http://an2/"], function() {
finish_cache2_test();
}),
true
@ -26,10 +26,10 @@ function run_test()
syncWithCacheIOThread(function() {
var storage = getCacheStorage("disk", LoadContextInfo.default);
storage.asyncVisitStorage(
new VisitCallback(2, 24, ["http://an2/", "anon&uri=http://an2/"], function() {
new VisitCallback(2, 24, ["http://an2/"], function() {
storage = getCacheStorage("disk", LoadContextInfo.anonymous);
storage.asyncVisitStorage(
new VisitCallback(0, 0, [], function() {
new VisitCallback(0, 0, ["http://an2/"], function() {
finish_cache2_test();
}),
true

View File

@ -0,0 +1,43 @@
/* 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/. */
// First, parse and save the incoming arguments ("?storage=name&context=key")
// Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
var search = window.location.href.match(/^.*\?(.*)$/);
var searchParams = new URLSearchParams(search ? search[1] : '');
var storage = searchParams.get('storage');
var context = searchParams.get('context');
// The context is in a format as used by the HTTP cache v2 back end
var [context, isAnon, isInBrowser, appId, isPrivate] = context.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
if (appId)
appId = appId.match(/i(\d+),/)[1];
function $(id) { return document.getElementById(id) || {}; }
// Initialize the context UI controls at the start according what we got in the "context=" argument
addEventListener('DOMContentLoaded', function() {
$('anon').checked = !!isAnon;
$('inbrowser').checked = !!isInBrowser;
$('appid').value = appId || '';
$('priv').checked = !!isPrivate;
}, false);
// When user presses the [Update] button, we build a new context key according the UI control
// values and navigate to a new about:cache?storage=<name>&context=<key> URL.
function navigate()
{
context = '';
if ($('anon').checked)
context += 'a,';
if ($('inbrowser').checked)
context += 'b,';
if ($('appid').value)
context += 'i' + $('appid').value + ',';
if ($('priv').checked)
context += 'p,';
window.location.href = 'about:cache?storage=' + storage + '&context=' + context;
}

View File

@ -0,0 +1,6 @@
# 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/.
toolkit.jar:
+ content/global/aboutCache.js (content/aboutCache.js)

View File

@ -0,0 +1,7 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ['jar.mn']

View File

@ -9,6 +9,7 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
PARALLEL_DIRS += ['remote']
PARALLEL_DIRS += [
'aboutcache',
'aboutmemory',
'alerts',
'apppicker',