mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 17:23:59 +00:00
1638 lines
40 KiB
C++
1638 lines
40 KiB
C++
/* 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 "CacheLog.h"
|
|
#include "CacheFile.h"
|
|
|
|
#include "CacheFileChunk.h"
|
|
#include "CacheFileInputStream.h"
|
|
#include "CacheFileOutputStream.h"
|
|
#include "nsITimer.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include <algorithm>
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsProxyRelease.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
#define kMetadataWriteDelay 5000
|
|
|
|
class NotifyCacheFileListenerEvent : public nsRunnable {
|
|
public:
|
|
NotifyCacheFileListenerEvent(CacheFileListener *aCallback,
|
|
nsresult aResult,
|
|
bool aIsNew)
|
|
: mCallback(aCallback)
|
|
, mRV(aResult)
|
|
, mIsNew(aIsNew)
|
|
{
|
|
LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
|
|
"[this=%p]", this));
|
|
MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent);
|
|
}
|
|
|
|
~NotifyCacheFileListenerEvent()
|
|
{
|
|
LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
|
|
"[this=%p]", this));
|
|
MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent);
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
|
|
|
|
mCallback->OnFileReady(mRV, mIsNew);
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
nsCOMPtr<CacheFileListener> mCallback;
|
|
nsresult mRV;
|
|
bool mIsNew;
|
|
};
|
|
|
|
class NotifyChunkListenerEvent : public nsRunnable {
|
|
public:
|
|
NotifyChunkListenerEvent(CacheFileChunkListener *aCallback,
|
|
nsresult aResult,
|
|
uint32_t aChunkIdx,
|
|
CacheFileChunk *aChunk)
|
|
: mCallback(aCallback)
|
|
, mRV(aResult)
|
|
, mChunkIdx(aChunkIdx)
|
|
, mChunk(aChunk)
|
|
{
|
|
LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
|
|
this));
|
|
MOZ_COUNT_CTOR(NotifyChunkListenerEvent);
|
|
}
|
|
|
|
~NotifyChunkListenerEvent()
|
|
{
|
|
LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
|
|
this));
|
|
MOZ_COUNT_DTOR(NotifyChunkListenerEvent);
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
|
|
|
|
mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
nsCOMPtr<CacheFileChunkListener> mCallback;
|
|
nsresult mRV;
|
|
uint32_t mChunkIdx;
|
|
nsRefPtr<CacheFileChunk> mChunk;
|
|
};
|
|
|
|
class MetadataWriteTimer : public nsITimerCallback
|
|
{
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSITIMERCALLBACK
|
|
|
|
MetadataWriteTimer(CacheFile *aFile);
|
|
nsresult Fire();
|
|
nsresult Cancel();
|
|
bool ShouldFireNew();
|
|
|
|
protected:
|
|
virtual ~MetadataWriteTimer();
|
|
|
|
nsCOMPtr<nsIWeakReference> mFile;
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
nsCOMPtr<nsIEventTarget> mTarget;
|
|
PRIntervalTime mFireTime;
|
|
};
|
|
|
|
NS_IMPL_ADDREF(MetadataWriteTimer)
|
|
NS_IMPL_RELEASE(MetadataWriteTimer)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
|
|
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
|
|
NS_INTERFACE_MAP_END_THREADSAFE
|
|
|
|
MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile)
|
|
: mFireTime(0)
|
|
{
|
|
LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]",
|
|
this, aFile));
|
|
MOZ_COUNT_CTOR(MetadataWriteTimer);
|
|
|
|
mFile = do_GetWeakReference(static_cast<CacheFileChunkListener *>(aFile));
|
|
mTarget = NS_GetCurrentThread();
|
|
}
|
|
|
|
MetadataWriteTimer::~MetadataWriteTimer()
|
|
{
|
|
LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this));
|
|
MOZ_COUNT_DTOR(MetadataWriteTimer);
|
|
|
|
NS_ProxyRelease(mTarget, mTimer.forget().get());
|
|
NS_ProxyRelease(mTarget, mFile.forget().get());
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MetadataWriteTimer::Notify(nsITimer *aTimer)
|
|
{
|
|
LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer));
|
|
|
|
MOZ_ASSERT(aTimer == mTimer);
|
|
|
|
nsCOMPtr<nsISupports> supp = do_QueryReferent(mFile);
|
|
if (!supp)
|
|
return NS_OK;
|
|
|
|
CacheFile *file = static_cast<CacheFile *>(
|
|
static_cast<CacheFileChunkListener *>(supp.get()));
|
|
|
|
CacheFileAutoLock lock(file);
|
|
|
|
if (file->mTimer != this)
|
|
return NS_OK;
|
|
|
|
if (file->mMemoryOnly)
|
|
return NS_OK;
|
|
|
|
file->WriteMetadataIfNeeded();
|
|
file->mTimer = nullptr;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MetadataWriteTimer::Fire()
|
|
{
|
|
LOG(("MetadataWriteTimer::Fire() [this=%p]", this));
|
|
|
|
nsresult rv;
|
|
|
|
#ifdef DEBUG
|
|
bool onCurrentThread = false;
|
|
rv = mTarget->IsOnCurrentThread(&onCurrentThread);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread);
|
|
#endif
|
|
|
|
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mTimer->InitWithCallback(this, kMetadataWriteDelay,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mFireTime = PR_IntervalNow();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MetadataWriteTimer::Cancel()
|
|
{
|
|
LOG(("MetadataWriteTimer::Cancel() [this=%p]", this));
|
|
return mTimer->Cancel();
|
|
}
|
|
|
|
bool
|
|
MetadataWriteTimer::ShouldFireNew()
|
|
{
|
|
uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime);
|
|
|
|
if (delta > kMetadataWriteDelay / 2) {
|
|
LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]",
|
|
this));
|
|
return true;
|
|
}
|
|
|
|
LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]",
|
|
this));
|
|
return false;
|
|
}
|
|
|
|
class DoomFileHelper : public CacheFileIOListener
|
|
{
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
DoomFileHelper(CacheFileListener *aListener)
|
|
: mListener(aListener)
|
|
{
|
|
MOZ_COUNT_CTOR(DoomFileHelper);
|
|
}
|
|
|
|
|
|
NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
|
|
nsresult aResult)
|
|
{
|
|
MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
|
|
{
|
|
MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
mListener->OnFileDoomed(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
private:
|
|
virtual ~DoomFileHelper()
|
|
{
|
|
MOZ_COUNT_DTOR(DoomFileHelper);
|
|
}
|
|
|
|
nsCOMPtr<CacheFileListener> mListener;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener)
|
|
|
|
|
|
// We try to write metadata when the last reference to CacheFile is released.
|
|
// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and
|
|
// if writing fails synchronously the listener is released and lock in
|
|
// CacheFile::Release() is re-entered. This helper class ensures that the
|
|
// listener is released outside the lock in case of synchronous failure.
|
|
class MetadataListenerHelper : public CacheFileMetadataListener
|
|
{
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
MetadataListenerHelper(CacheFile *aFile)
|
|
: mFile(aFile)
|
|
{
|
|
MOZ_COUNT_CTOR(MetadataListenerHelper);
|
|
mListener = static_cast<CacheFileMetadataListener *>(aFile);
|
|
}
|
|
|
|
NS_IMETHOD OnMetadataRead(nsresult aResult)
|
|
{
|
|
MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
NS_IMETHOD OnMetadataWritten(nsresult aResult)
|
|
{
|
|
mFile = nullptr;
|
|
return mListener->OnMetadataWritten(aResult);
|
|
}
|
|
|
|
private:
|
|
virtual ~MetadataListenerHelper()
|
|
{
|
|
MOZ_COUNT_DTOR(MetadataListenerHelper);
|
|
if (mFile) {
|
|
mFile->ReleaseOutsideLock(mListener.forget().get());
|
|
}
|
|
}
|
|
|
|
CacheFile* mFile;
|
|
nsCOMPtr<CacheFileMetadataListener> mListener;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener)
|
|
|
|
|
|
NS_IMPL_ADDREF(CacheFile)
|
|
NS_IMETHODIMP_(nsrefcnt)
|
|
CacheFile::Release()
|
|
{
|
|
NS_PRECONDITION(0 != mRefCnt, "dup release");
|
|
nsrefcnt count = --mRefCnt;
|
|
NS_LOG_RELEASE(this, count, "CacheFile");
|
|
|
|
MOZ_ASSERT(count != 0, "Unexpected");
|
|
|
|
if (count == 1) {
|
|
bool deleteFile = false;
|
|
|
|
// Not using CacheFileAutoLock since it hard-refers the file
|
|
// and in it's destructor reenters this method.
|
|
Lock();
|
|
|
|
if (mMemoryOnly) {
|
|
deleteFile = true;
|
|
}
|
|
else if (mMetadata) {
|
|
WriteMetadataIfNeeded();
|
|
if (mWritingMetadata) {
|
|
MOZ_ASSERT(mRefCnt > 1);
|
|
} else {
|
|
if (mRefCnt == 1)
|
|
deleteFile = true;
|
|
}
|
|
}
|
|
|
|
Unlock();
|
|
|
|
if (!deleteFile) {
|
|
return count;
|
|
}
|
|
|
|
NS_LOG_RELEASE(this, 0, "CacheFile");
|
|
delete (this);
|
|
return 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN(CacheFile)
|
|
NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
|
|
NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
|
|
NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
|
|
mozilla::net::CacheFileChunkListener)
|
|
NS_INTERFACE_MAP_END_THREADSAFE
|
|
|
|
CacheFile::CacheFile()
|
|
: mLock("CacheFile.mLock")
|
|
, mOpeningFile(false)
|
|
, mReady(false)
|
|
, mMemoryOnly(false)
|
|
, mDataAccessed(false)
|
|
, mDataIsDirty(false)
|
|
, mWritingMetadata(false)
|
|
, mStatus(NS_OK)
|
|
, mDataSize(-1)
|
|
, mOutput(nullptr)
|
|
{
|
|
LOG(("CacheFile::CacheFile() [this=%p]", this));
|
|
|
|
NS_ADDREF(this);
|
|
MOZ_COUNT_CTOR(CacheFile);
|
|
}
|
|
|
|
CacheFile::~CacheFile()
|
|
{
|
|
LOG(("CacheFile::~CacheFile() [this=%p]", this));
|
|
|
|
MOZ_COUNT_DTOR(CacheFile);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::Init(const nsACString &aKey,
|
|
bool aCreateNew,
|
|
bool aMemoryOnly,
|
|
bool aPriority,
|
|
bool aKeyIsHash,
|
|
CacheFileListener *aCallback)
|
|
{
|
|
MOZ_ASSERT(!mListener);
|
|
MOZ_ASSERT(!mHandle);
|
|
|
|
nsresult rv;
|
|
|
|
mKey = aKey;
|
|
mMemoryOnly = aMemoryOnly;
|
|
mKeyIsHash = aKeyIsHash;
|
|
|
|
LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
|
|
"listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly, aCallback));
|
|
|
|
if (mMemoryOnly) {
|
|
MOZ_ASSERT(!aCallback);
|
|
|
|
MOZ_ASSERT(!mKeyIsHash);
|
|
mMetadata = new CacheFileMetadata(mKey);
|
|
mReady = true;
|
|
mDataSize = mMetadata->Offset();
|
|
return NS_OK;
|
|
}
|
|
else {
|
|
uint32_t flags;
|
|
if (aCreateNew) {
|
|
MOZ_ASSERT(!aCallback);
|
|
flags = CacheFileIOManager::CREATE_NEW;
|
|
|
|
// make sure we can use this entry immediately
|
|
MOZ_ASSERT(!mKeyIsHash);
|
|
mMetadata = new CacheFileMetadata(mKey);
|
|
mReady = true;
|
|
mDataSize = mMetadata->Offset();
|
|
}
|
|
else
|
|
flags = CacheFileIOManager::CREATE;
|
|
|
|
if (aPriority)
|
|
flags |= CacheFileIOManager::PRIORITY;
|
|
if (aKeyIsHash)
|
|
flags |= CacheFileIOManager::NOHASH;
|
|
|
|
mOpeningFile = true;
|
|
mListener = aCallback;
|
|
rv = CacheFileIOManager::OpenFile(mKey, flags, this);
|
|
if (NS_FAILED(rv)) {
|
|
mListener = nullptr;
|
|
mOpeningFile = false;
|
|
|
|
if (aCreateNew) {
|
|
NS_WARNING("Forcing memory-only entry since OpenFile failed");
|
|
LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
|
|
"synchronously. We can continue in memory-only mode since "
|
|
"aCreateNew == true. [this=%p]", this));
|
|
|
|
mMemoryOnly = true;
|
|
}
|
|
else if (rv == NS_ERROR_NOT_INITIALIZED) {
|
|
NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
|
|
"initialized.");
|
|
LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
|
|
"initializing entry as memory-only. [this=%p]", this));
|
|
|
|
mMemoryOnly = true;
|
|
MOZ_ASSERT(!mKeyIsHash);
|
|
mMetadata = new CacheFileMetadata(mKey);
|
|
mReady = true;
|
|
mDataSize = mMetadata->Offset();
|
|
|
|
nsRefPtr<NotifyCacheFileListenerEvent> ev;
|
|
ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
|
|
rv = NS_DispatchToCurrentThread(ev);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
else {
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
nsresult rv;
|
|
|
|
uint32_t index = aChunk->Index();
|
|
|
|
LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
|
|
this, aResult, aChunk, index));
|
|
|
|
// TODO handle ERROR state
|
|
|
|
if (HaveChunkListeners(index)) {
|
|
rv = NotifyChunkListeners(index, aResult, aChunk);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
nsresult rv;
|
|
|
|
LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]",
|
|
this, aResult, aChunk, aChunk->Index()));
|
|
|
|
MOZ_ASSERT(!mMemoryOnly);
|
|
|
|
// TODO handle ERROR state
|
|
|
|
if (NS_FAILED(aResult)) {
|
|
// TODO ??? doom entry
|
|
// TODO mark this chunk as memory only, since it wasn't written to disk and
|
|
// therefore cannot be released from memory
|
|
// LOG
|
|
}
|
|
|
|
if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
|
|
// update hash value in metadata
|
|
mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
|
|
}
|
|
|
|
// notify listeners if there is any
|
|
if (HaveChunkListeners(aChunk->Index())) {
|
|
// don't release the chunk since there are some listeners queued
|
|
rv = NotifyChunkListeners(aChunk->Index(), NS_OK, aChunk);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
MOZ_ASSERT(aChunk->mRefCnt != 2);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
if (aChunk->mRefCnt != 2) {
|
|
LOG(("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
|
|
" refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]",
|
|
this, aChunk));
|
|
|
|
aChunk->mRemovingChunk = true;
|
|
ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
|
|
aChunk->mFile.forget().get()));
|
|
mCachedChunks.Put(aChunk->Index(), aChunk);
|
|
mChunks.Remove(aChunk->Index());
|
|
WriteMetadataIfNeeded();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
|
|
CacheFileChunk *aChunk)
|
|
{
|
|
MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnChunkUpdated(CacheFileChunk *aChunk)
|
|
{
|
|
MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<CacheFileListener> listener;
|
|
bool isNew = false;
|
|
nsresult retval = NS_OK;
|
|
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
MOZ_ASSERT(mOpeningFile);
|
|
MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) ||
|
|
(NS_FAILED(aResult) && !aHandle));
|
|
MOZ_ASSERT((mListener && !mMetadata) || // !createNew
|
|
(!mListener && mMetadata)); // createNew
|
|
MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry
|
|
|
|
LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08x, handle=%p]",
|
|
this, aResult, aHandle));
|
|
|
|
mOpeningFile = false;
|
|
|
|
if (mMemoryOnly) {
|
|
// We can be here only in case the entry was initilized as createNew and
|
|
// SetMemoryOnly() was called.
|
|
|
|
// Just don't store the handle into mHandle and exit
|
|
return NS_OK;
|
|
}
|
|
else if (NS_FAILED(aResult)) {
|
|
if (mMetadata) {
|
|
// This entry was initialized as createNew, just switch to memory-only
|
|
// mode.
|
|
NS_WARNING("Forcing memory-only entry since OpenFile failed");
|
|
LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
|
|
"failed asynchronously. We can continue in memory-only mode since "
|
|
"aCreateNew == true. [this=%p]", this));
|
|
|
|
mMemoryOnly = true;
|
|
return NS_OK;
|
|
}
|
|
else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
|
|
// CacheFileIOManager doesn't have mCacheDirectory, switch to
|
|
// memory-only mode.
|
|
NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
|
|
"have mCacheDirectory.");
|
|
LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
|
|
"mCacheDirectory, initializing entry as memory-only. [this=%p]",
|
|
this));
|
|
|
|
mMemoryOnly = true;
|
|
MOZ_ASSERT(!mKeyIsHash);
|
|
mMetadata = new CacheFileMetadata(mKey);
|
|
mReady = true;
|
|
mDataSize = mMetadata->Offset();
|
|
|
|
isNew = true;
|
|
retval = NS_OK;
|
|
}
|
|
else {
|
|
// CacheFileIOManager::OpenFile() failed for another reason.
|
|
isNew = false;
|
|
retval = aResult;
|
|
}
|
|
|
|
mListener.swap(listener);
|
|
}
|
|
else {
|
|
mHandle = aHandle;
|
|
|
|
if (mMetadata) {
|
|
// The entry was initialized as createNew, don't try to read metadata.
|
|
mMetadata->SetHandle(mHandle);
|
|
|
|
// Write all cached chunks, otherwise thay may stay unwritten.
|
|
mCachedChunks.Enumerate(&CacheFile::WriteAllCachedChunks, this);
|
|
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (listener) {
|
|
listener->OnFileReady(retval, isNew);
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(aResult));
|
|
MOZ_ASSERT(!mMetadata);
|
|
MOZ_ASSERT(mListener);
|
|
|
|
mMetadata = new CacheFileMetadata(mHandle, mKey, mKeyIsHash);
|
|
|
|
rv = mMetadata->ReadMetadata(this);
|
|
if (NS_FAILED(rv)) {
|
|
mListener.swap(listener);
|
|
listener->OnFileReady(rv, false);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
|
|
nsresult aResult)
|
|
{
|
|
MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
|
|
{
|
|
MOZ_CRASH("CacheFile::OnDataRead should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnMetadataRead(nsresult aResult)
|
|
{
|
|
MOZ_ASSERT(mListener);
|
|
|
|
LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
|
|
|
|
bool isNew = false;
|
|
if (NS_SUCCEEDED(aResult)) {
|
|
mReady = true;
|
|
mDataSize = mMetadata->Offset();
|
|
if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
|
|
isNew = true;
|
|
mMetadata->MarkDirty();
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<CacheFileListener> listener;
|
|
mListener.swap(listener);
|
|
listener->OnFileReady(aResult, isNew);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnMetadataWritten(nsresult aResult)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08x]", this, aResult));
|
|
|
|
MOZ_ASSERT(mWritingMetadata);
|
|
mWritingMetadata = false;
|
|
|
|
MOZ_ASSERT(!mMemoryOnly);
|
|
MOZ_ASSERT(!mOpeningFile);
|
|
|
|
if (NS_FAILED(aResult)) {
|
|
// TODO close streams with an error ???
|
|
}
|
|
|
|
if (mOutput || mInputs.Length() || mChunks.Count())
|
|
return NS_OK;
|
|
|
|
if (IsDirty())
|
|
WriteMetadataIfNeeded();
|
|
|
|
if (!mWritingMetadata) {
|
|
LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
|
|
this));
|
|
CacheFileIOManager::ReleaseNSPRHandle(mHandle);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
nsCOMPtr<CacheFileListener> listener;
|
|
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
MOZ_ASSERT(mListener);
|
|
|
|
LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08x, handle=%p]",
|
|
this, aResult, aHandle));
|
|
|
|
mListener.swap(listener);
|
|
}
|
|
|
|
listener->OnFileDoomed(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
|
|
{
|
|
MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OpenInputStream(nsIInputStream **_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
|
|
|
|
if (!mReady) {
|
|
LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
|
|
this));
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
CacheFileInputStream *input = new CacheFileInputStream(this);
|
|
|
|
LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
|
|
input, this));
|
|
|
|
mInputs.AppendElement(input);
|
|
NS_ADDREF(input);
|
|
|
|
mDataAccessed = true;
|
|
NS_ADDREF(*_retval = input);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
|
|
|
|
if (!mReady) {
|
|
LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
|
|
this));
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (mOutput) {
|
|
LOG(("CacheFile::OpenOutputStream() - We already have output stream %p "
|
|
"[this=%p]", mOutput, this));
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mOutput = new CacheFileOutputStream(this, aCloseListener);
|
|
|
|
LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p "
|
|
"[this=%p]", mOutput, this));
|
|
|
|
mDataAccessed = true;
|
|
NS_ADDREF(*_retval = mOutput);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::SetMemoryOnly()
|
|
{
|
|
LOG(("CacheFile::SetMemoryOnly() aMemoryOnly=%d [this=%p]",
|
|
mMemoryOnly, this));
|
|
|
|
if (mMemoryOnly)
|
|
return NS_OK;
|
|
|
|
MOZ_ASSERT(mReady);
|
|
|
|
if (!mReady) {
|
|
LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
|
|
this));
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (mDataAccessed) {
|
|
LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]", this));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// TODO what to do when this isn't a new entry and has an existing metadata???
|
|
mMemoryOnly = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::Doom(CacheFileListener *aCallback)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
|
|
|
|
LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
|
|
|
|
nsresult rv;
|
|
|
|
if (mMemoryOnly) {
|
|
// TODO what exactly to do here?
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<CacheFileIOListener> listener;
|
|
if (aCallback)
|
|
listener = new DoomFileHelper(aCallback);
|
|
|
|
if (mHandle) {
|
|
rv = CacheFileIOManager::DoomFile(mHandle, listener);
|
|
} else {
|
|
rv = CacheFileIOManager::DoomFileByKey(mKey, listener);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::ThrowMemoryCachedData()
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
|
|
|
|
if (mOpeningFile) {
|
|
// mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
|
|
// entries from being purged.
|
|
|
|
LOG(("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
|
|
"entry is still opening the file [this=%p]", this));
|
|
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
mCachedChunks.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetElement(const char *aKey, const char **_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
*_retval = mMetadata->GetElement(aKey);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::SetElement(const char *aKey, const char *aValue)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
PostWriteTimer();
|
|
return mMetadata->SetElement(aKey, aValue);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::ElementsSize(uint32_t *_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
if (!mMetadata)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*_retval = mMetadata->ElementsSize();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::SetExpirationTime(uint32_t aExpirationTime)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
PostWriteTimer();
|
|
return mMetadata->SetExpirationTime(aExpirationTime);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetExpirationTime(uint32_t *_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
return mMetadata->GetExpirationTime(_retval);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::SetLastModified(uint32_t aLastModified)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
PostWriteTimer();
|
|
return mMetadata->SetLastModified(aLastModified);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetLastModified(uint32_t *_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
return mMetadata->GetLastModified(_retval);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetLastFetched(uint32_t *_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
return mMetadata->GetLastFetched(_retval);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetFetchCount(uint32_t *_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
MOZ_ASSERT(mMetadata);
|
|
NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
|
|
|
|
return mMetadata->GetFetchCount(_retval);
|
|
}
|
|
|
|
void
|
|
CacheFile::Lock()
|
|
{
|
|
mLock.Lock();
|
|
}
|
|
|
|
void
|
|
CacheFile::Unlock()
|
|
{
|
|
nsTArray<nsISupports*> objs;
|
|
objs.SwapElements(mObjsToRelease);
|
|
|
|
mLock.Unlock();
|
|
|
|
for (uint32_t i = 0; i < objs.Length(); i++)
|
|
objs[i]->Release();
|
|
}
|
|
|
|
void
|
|
CacheFile::AssertOwnsLock()
|
|
{
|
|
mLock.AssertCurrentThreadOwns();
|
|
}
|
|
|
|
void
|
|
CacheFile::ReleaseOutsideLock(nsISupports *aObject)
|
|
{
|
|
AssertOwnsLock();
|
|
|
|
mObjsToRelease.AppendElement(aObject);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetChunk(uint32_t aIndex, bool aWriter,
|
|
CacheFileChunkListener *aCallback, CacheFileChunk **_retval)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
return GetChunkLocked(aIndex, aWriter, aCallback, _retval);
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::GetChunkLocked(uint32_t aIndex, bool aWriter,
|
|
CacheFileChunkListener *aCallback,
|
|
CacheFileChunk **_retval)
|
|
{
|
|
AssertOwnsLock();
|
|
|
|
LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%d, writer=%d, listener=%p]",
|
|
this, aIndex, aWriter, aCallback));
|
|
|
|
MOZ_ASSERT(mReady);
|
|
MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
|
|
MOZ_ASSERT((aWriter && !aCallback) || (!aWriter && aCallback));
|
|
|
|
nsresult rv;
|
|
|
|
nsRefPtr<CacheFileChunk> chunk;
|
|
if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
|
|
LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
|
|
chunk.get(), this));
|
|
|
|
if (chunk->IsReady() || aWriter) {
|
|
chunk.swap(*_retval);
|
|
}
|
|
else {
|
|
rv = QueueChunkListener(aIndex, aCallback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) {
|
|
LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
|
|
chunk.get(), this));
|
|
|
|
mChunks.Put(aIndex, chunk);
|
|
mCachedChunks.Remove(aIndex);
|
|
chunk->mFile = this;
|
|
chunk->mRemovingChunk = false;
|
|
|
|
MOZ_ASSERT(chunk->IsReady());
|
|
|
|
chunk.swap(*_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
int64_t off = aIndex * kChunkSize;
|
|
|
|
if (off < mDataSize) {
|
|
// We cannot be here if this is memory only entry since the chunk must exist
|
|
MOZ_ASSERT(!mMemoryOnly);
|
|
|
|
chunk = new CacheFileChunk(this, aIndex);
|
|
mChunks.Put(aIndex, chunk);
|
|
|
|
LOG(("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
|
|
"the disk [this=%p]", chunk.get(), this));
|
|
|
|
// Read the chunk from the disk
|
|
rv = chunk->Read(mHandle, std::min(static_cast<uint32_t>(mDataSize - off),
|
|
static_cast<uint32_t>(kChunkSize)),
|
|
mMetadata->GetHash(aIndex), this);
|
|
if (NS_FAILED(rv)) {
|
|
chunk->mRemovingChunk = true;
|
|
ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
|
|
chunk->mFile.forget().get()));
|
|
mChunks.Remove(aIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
if (aWriter) {
|
|
chunk.swap(*_retval);
|
|
}
|
|
else {
|
|
rv = QueueChunkListener(aIndex, aCallback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
else if (off == mDataSize) {
|
|
if (aWriter) {
|
|
// this listener is going to write to the chunk
|
|
chunk = new CacheFileChunk(this, aIndex);
|
|
mChunks.Put(aIndex, chunk);
|
|
|
|
LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
|
|
chunk.get(), this));
|
|
|
|
chunk->InitNew(this);
|
|
mMetadata->SetHash(aIndex, chunk->Hash());
|
|
|
|
if (HaveChunkListeners(aIndex)) {
|
|
rv = NotifyChunkListeners(aIndex, NS_OK, chunk);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
chunk.swap(*_retval);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
else {
|
|
if (aWriter) {
|
|
// this chunk was requested by writer, but we need to fill the gap first
|
|
|
|
// Fill with zero the last chunk if it is incomplete
|
|
if (mDataSize % kChunkSize) {
|
|
rv = PadChunkWithZeroes(mDataSize / kChunkSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
MOZ_ASSERT(!(mDataSize % kChunkSize));
|
|
}
|
|
|
|
uint32_t startChunk = mDataSize / kChunkSize;
|
|
|
|
if (mMemoryOnly) {
|
|
// We need to create all missing CacheFileChunks if this is memory-only
|
|
// entry
|
|
for (uint32_t i = startChunk ; i < aIndex ; i++) {
|
|
rv = PadChunkWithZeroes(i);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
else {
|
|
// We don't need to create CacheFileChunk for other empty chunks unless
|
|
// there is some input stream waiting for this chunk.
|
|
|
|
if (startChunk != aIndex) {
|
|
// Make sure the file contains zeroes at the end of the file
|
|
rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle,
|
|
startChunk * kChunkSize,
|
|
aIndex * kChunkSize,
|
|
nullptr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
for (uint32_t i = startChunk ; i < aIndex ; i++) {
|
|
if (HaveChunkListeners(i)) {
|
|
rv = PadChunkWithZeroes(i);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
else {
|
|
mMetadata->SetHash(i, kEmptyChunkHash);
|
|
mDataSize = (i + 1) * kChunkSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(mDataSize == off);
|
|
rv = GetChunkLocked(aIndex, true, nullptr, getter_AddRefs(chunk));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
chunk.swap(*_retval);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
if (mOutput) {
|
|
// the chunk doesn't exist but mOutput may create it
|
|
rv = QueueChunkListener(aIndex, aCallback);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
else {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::RemoveChunk(CacheFileChunk *aChunk)
|
|
{
|
|
nsresult rv;
|
|
|
|
// Avoid lock reentrancy by increasing the RefCnt
|
|
nsRefPtr<CacheFileChunk> chunk = aChunk;
|
|
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%d]",
|
|
this, aChunk, aChunk->Index()));
|
|
|
|
MOZ_ASSERT(mReady);
|
|
MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
|
|
|
|
if (aChunk->mRefCnt != 2) {
|
|
LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, "
|
|
"refcnt=%d]", this, aChunk, aChunk->mRefCnt.get()));
|
|
|
|
// somebody got the reference before the lock was acquired
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// We can be here iff the chunk is in the hash table
|
|
nsRefPtr<CacheFileChunk> chunkCheck;
|
|
mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
|
|
MOZ_ASSERT(chunkCheck == chunk);
|
|
|
|
// We also shouldn't have any queued listener for this chunk
|
|
ChunkListeners *listeners;
|
|
mChunkListeners.Get(chunk->Index(), &listeners);
|
|
MOZ_ASSERT(!listeners);
|
|
}
|
|
#endif
|
|
|
|
if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
|
|
LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk "
|
|
"[this=%p]", this));
|
|
|
|
mDataIsDirty = true;
|
|
|
|
rv = chunk->Write(mHandle, this);
|
|
if (NS_FAILED(rv)) {
|
|
// TODO ??? doom entry
|
|
// TODO mark this chunk as memory only, since it wasn't written to disk
|
|
// and therefore cannot be released from memory
|
|
// LOG
|
|
}
|
|
else {
|
|
// Chunk will be removed in OnChunkWritten if it is still unused
|
|
|
|
// chunk needs to be released under the lock to be able to rely on
|
|
// CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
|
|
chunk = nullptr;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p]",
|
|
this, chunk.get()));
|
|
|
|
chunk->mRemovingChunk = true;
|
|
ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
|
|
chunk->mFile.forget().get()));
|
|
mCachedChunks.Put(chunk->Index(), chunk);
|
|
mChunks.Remove(chunk->Index());
|
|
if (!mMemoryOnly)
|
|
WriteMetadataIfNeeded();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::RemoveInput(CacheFileInputStream *aInput)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput));
|
|
|
|
DebugOnly<bool> found;
|
|
found = mInputs.RemoveElement(aInput);
|
|
MOZ_ASSERT(found);
|
|
|
|
ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput));
|
|
|
|
if (!mMemoryOnly)
|
|
WriteMetadataIfNeeded();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::RemoveOutput(CacheFileOutputStream *aOutput)
|
|
{
|
|
AssertOwnsLock();
|
|
|
|
LOG(("CacheFile::RemoveOutput() [this=%p, output=%p]", this, aOutput));
|
|
|
|
if (mOutput != aOutput) {
|
|
LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring"
|
|
" call [this=%p]", this));
|
|
return NS_OK;
|
|
}
|
|
|
|
mOutput = nullptr;
|
|
|
|
// Cancel all queued chunk and update listeners that cannot be satisfied
|
|
NotifyListenersAboutOutputRemoval();
|
|
|
|
if (!mMemoryOnly)
|
|
WriteMetadataIfNeeded();
|
|
|
|
// Notify close listener as the last action
|
|
aOutput->NotifyCloseListener();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::NotifyChunkListener(CacheFileChunkListener *aCallback,
|
|
nsIEventTarget *aTarget,
|
|
nsresult aResult,
|
|
uint32_t aChunkIdx,
|
|
CacheFileChunk *aChunk)
|
|
{
|
|
LOG(("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
|
|
"rv=0x%08x, idx=%d, chunk=%p]", this, aCallback, aTarget, aResult,
|
|
aChunkIdx, aChunk));
|
|
|
|
nsresult rv;
|
|
nsRefPtr<NotifyChunkListenerEvent> ev;
|
|
ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
|
|
if (aTarget)
|
|
rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
|
|
else
|
|
rv = NS_DispatchToCurrentThread(ev);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::QueueChunkListener(uint32_t aIndex,
|
|
CacheFileChunkListener *aCallback)
|
|
{
|
|
LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%d, listener=%p]",
|
|
this, aIndex, aCallback));
|
|
|
|
AssertOwnsLock();
|
|
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
ChunkListenerItem *item = new ChunkListenerItem();
|
|
item->mTarget = NS_GetCurrentThread();
|
|
item->mCallback = aCallback;
|
|
|
|
ChunkListeners *listeners;
|
|
if (!mChunkListeners.Get(aIndex, &listeners)) {
|
|
listeners = new ChunkListeners();
|
|
mChunkListeners.Put(aIndex, listeners);
|
|
}
|
|
|
|
listeners->mItems.AppendElement(item);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
|
|
CacheFileChunk *aChunk)
|
|
{
|
|
LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%d, rv=0x%08x, "
|
|
"chunk=%p]", this, aIndex, aResult, aChunk));
|
|
|
|
AssertOwnsLock();
|
|
|
|
nsresult rv, rv2;
|
|
|
|
ChunkListeners *listeners;
|
|
mChunkListeners.Get(aIndex, &listeners);
|
|
MOZ_ASSERT(listeners);
|
|
|
|
rv = NS_OK;
|
|
for (uint32_t i = 0 ; i < listeners->mItems.Length() ; i++) {
|
|
ChunkListenerItem *item = listeners->mItems[i];
|
|
rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex,
|
|
aChunk);
|
|
if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
|
|
rv = rv2;
|
|
delete item;
|
|
}
|
|
|
|
mChunkListeners.Remove(aIndex);
|
|
|
|
return rv;
|
|
}
|
|
|
|
bool
|
|
CacheFile::HaveChunkListeners(uint32_t aIndex)
|
|
{
|
|
ChunkListeners *listeners;
|
|
mChunkListeners.Get(aIndex, &listeners);
|
|
return !!listeners;
|
|
}
|
|
|
|
void
|
|
CacheFile::NotifyListenersAboutOutputRemoval()
|
|
{
|
|
LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
|
|
|
|
AssertOwnsLock();
|
|
|
|
// First fail all chunk listeners that wait for non-existent chunk
|
|
mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk,
|
|
this);
|
|
|
|
// Fail all update listeners
|
|
mChunks.Enumerate(&CacheFile::FailUpdateListeners, this);
|
|
}
|
|
|
|
bool
|
|
CacheFile::DataSize(int64_t* aSize)
|
|
{
|
|
CacheFileAutoLock lock(this);
|
|
|
|
if (mOutput)
|
|
return false;
|
|
|
|
*aSize = mDataSize;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CacheFile::IsDirty()
|
|
{
|
|
return mDataIsDirty || mMetadata->IsDirty();
|
|
}
|
|
|
|
void
|
|
CacheFile::WriteMetadataIfNeeded()
|
|
{
|
|
LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
|
|
|
|
nsresult rv;
|
|
|
|
AssertOwnsLock();
|
|
MOZ_ASSERT(!mMemoryOnly);
|
|
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
|
|
if (NS_FAILED(mStatus))
|
|
return;
|
|
|
|
if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
|
|
mWritingMetadata || mOpeningFile)
|
|
return;
|
|
|
|
LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]",
|
|
this));
|
|
|
|
nsRefPtr<MetadataListenerHelper> mlh = new MetadataListenerHelper(this);
|
|
|
|
rv = mMetadata->WriteMetadata(mDataSize, mlh);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mWritingMetadata = true;
|
|
mDataIsDirty = false;
|
|
}
|
|
else {
|
|
LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed "
|
|
"[this=%p]", this));
|
|
// TODO: close streams with error
|
|
if (NS_SUCCEEDED(mStatus))
|
|
mStatus = rv;
|
|
}
|
|
}
|
|
|
|
void
|
|
CacheFile::PostWriteTimer()
|
|
{
|
|
LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
|
|
|
|
nsresult rv;
|
|
|
|
AssertOwnsLock();
|
|
|
|
if (mTimer) {
|
|
if (mTimer->ShouldFireNew()) {
|
|
LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]",
|
|
this));
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
else {
|
|
LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this));
|
|
return;
|
|
}
|
|
}
|
|
|
|
mTimer = new MetadataWriteTimer(this);
|
|
|
|
rv = mTimer->Fire();
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x "
|
|
"[this=%p]", rv, this));
|
|
}
|
|
}
|
|
|
|
PLDHashOperator
|
|
CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
|
|
nsRefPtr<CacheFileChunk>& aChunk,
|
|
void* aClosure)
|
|
{
|
|
CacheFile *file = static_cast<CacheFile*>(aClosure);
|
|
|
|
LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%d, chunk=%p]",
|
|
file, aIdx, aChunk.get()));
|
|
|
|
file->mChunks.Put(aIdx, aChunk);
|
|
aChunk->mFile = file;
|
|
aChunk->mRemovingChunk = false;
|
|
|
|
MOZ_ASSERT(aChunk->IsReady());
|
|
|
|
NS_ADDREF(aChunk);
|
|
file->ReleaseOutsideLock(aChunk);
|
|
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
PLDHashOperator
|
|
CacheFile::FailListenersIfNonExistentChunk(
|
|
const uint32_t& aIdx,
|
|
nsAutoPtr<ChunkListeners>& aListeners,
|
|
void* aClosure)
|
|
{
|
|
CacheFile *file = static_cast<CacheFile*>(aClosure);
|
|
|
|
LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%d]",
|
|
file, aIdx));
|
|
|
|
nsRefPtr<CacheFileChunk> chunk;
|
|
file->mChunks.Get(aIdx, getter_AddRefs(chunk));
|
|
if (chunk) {
|
|
MOZ_ASSERT(!chunk->IsReady());
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
for (uint32_t i = 0 ; i < aListeners->mItems.Length() ; i++) {
|
|
ChunkListenerItem *item = aListeners->mItems[i];
|
|
file->NotifyChunkListener(item->mCallback, item->mTarget,
|
|
NS_ERROR_NOT_AVAILABLE, aIdx, nullptr);
|
|
delete item;
|
|
}
|
|
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
PLDHashOperator
|
|
CacheFile::FailUpdateListeners(
|
|
const uint32_t& aIdx,
|
|
nsRefPtr<CacheFileChunk>& aChunk,
|
|
void* aClosure)
|
|
{
|
|
#ifdef PR_LOGGING
|
|
CacheFile *file = static_cast<CacheFile*>(aClosure);
|
|
#endif
|
|
|
|
LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%d]",
|
|
file, aIdx));
|
|
|
|
if (aChunk->IsReady()) {
|
|
aChunk->NotifyUpdateListeners();
|
|
}
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
nsresult
|
|
CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx)
|
|
{
|
|
AssertOwnsLock();
|
|
|
|
// This method is used to pad last incomplete chunk with zeroes or create
|
|
// a new chunk full of zeroes
|
|
MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx);
|
|
|
|
nsresult rv;
|
|
nsRefPtr<CacheFileChunk> chunk;
|
|
rv = GetChunkLocked(aChunkIdx, true, nullptr, getter_AddRefs(chunk));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LOG(("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
|
|
" [this=%p]", aChunkIdx, chunk->DataSize(), kChunkSize - 1, this));
|
|
|
|
chunk->EnsureBufSize(kChunkSize);
|
|
memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize());
|
|
|
|
chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(),
|
|
false);
|
|
|
|
ReleaseOutsideLock(chunk.forget().get());
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // net
|
|
} // mozilla
|