forked from mirrors/gecko-dev
Before P1, GetCurrentThreadSerialEventTarget would have always returned the same data as NS_GetCurrentThread, making the comment incorrect Now it will properly return the running TaskQueue if any. This change of name more clearly exposes what they are doing, as we aren't always dealing with threads directly; but a nsISerialEventTarget Differential Revision: https://phabricator.services.mozilla.com/D80354
1307 lines
40 KiB
C++
1307 lines
40 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* 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 "nsICache.h"
|
|
#include "nsCache.h"
|
|
#include "nsCacheService.h"
|
|
#include "nsCacheEntryDescriptor.h"
|
|
#include "nsCacheEntry.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsCRT.h"
|
|
#include "nsThreadUtils.h"
|
|
#include <algorithm>
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
|
|
#define kMinDecompressReadBufLen 1024
|
|
#define kMinCompressWriteBufLen 1024
|
|
|
|
/******************************************************************************
|
|
* nsAsyncDoomEvent
|
|
*****************************************************************************/
|
|
|
|
class nsAsyncDoomEvent : public mozilla::Runnable {
|
|
public:
|
|
nsAsyncDoomEvent(nsCacheEntryDescriptor* descriptor,
|
|
nsICacheListener* listener)
|
|
: mozilla::Runnable("nsAsyncDoomEvent") {
|
|
mDescriptor = descriptor;
|
|
mListener = listener;
|
|
mEventTarget = GetCurrentEventTarget();
|
|
// We addref the listener here and release it in nsNotifyDoomListener
|
|
// on the callers thread. If posting of nsNotifyDoomListener event fails
|
|
// we leak the listener which is better than releasing it on a wrong
|
|
// thread.
|
|
NS_IF_ADDREF(mListener);
|
|
}
|
|
|
|
NS_IMETHOD Run() override {
|
|
nsresult status = NS_OK;
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSASYNCDOOMEVENT_RUN));
|
|
|
|
if (mDescriptor->mCacheEntry) {
|
|
status = nsCacheService::gService->DoomEntry_Internal(
|
|
mDescriptor->mCacheEntry, true);
|
|
} else if (!mDescriptor->mDoomedOnClose) {
|
|
status = NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
if (mListener) {
|
|
mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
|
|
NS_DISPATCH_NORMAL);
|
|
// posted event will release the reference on the correct thread
|
|
mListener = nullptr;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
RefPtr<nsCacheEntryDescriptor> mDescriptor;
|
|
nsICacheListener* mListener;
|
|
nsCOMPtr<nsIEventTarget> mEventTarget;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsCacheEntryDescriptor, nsICacheEntryDescriptor,
|
|
nsICacheEntryInfo)
|
|
|
|
nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry* entry,
|
|
nsCacheAccessMode accessGranted)
|
|
: mCacheEntry(entry),
|
|
mAccessGranted(accessGranted),
|
|
mOutputWrapper(nullptr),
|
|
mLock("nsCacheEntryDescriptor.mLock"),
|
|
mAsyncDoomPending(false),
|
|
mDoomedOnClose(false),
|
|
mClosingDescriptor(false) {
|
|
PR_INIT_CLIST(this);
|
|
// We need to make sure the cache service lives for the entire lifetime
|
|
// of the descriptor
|
|
mCacheService = nsCacheService::GlobalInstance();
|
|
}
|
|
|
|
nsCacheEntryDescriptor::~nsCacheEntryDescriptor() {
|
|
// No need to close if the cache entry has already been severed. This
|
|
// helps avoid a shutdown assertion (bug 285519) that is caused when
|
|
// consumers end up holding onto these objects past xpcom-shutdown. It's
|
|
// okay for them to do that because the cache service calls our Close
|
|
// method during xpcom-shutdown, so we don't need to complain about it.
|
|
if (mCacheEntry) Close();
|
|
|
|
NS_ASSERTION(mInputWrappers.IsEmpty(), "We have still some input wrapper!");
|
|
NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!");
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetClientID(nsACString& aClientID) {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID));
|
|
if (!mCacheEntry) {
|
|
aClientID.Truncate();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return ClientIDFromCacheKey(*(mCacheEntry->Key()), aClientID);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetDeviceID(nsACString& aDeviceID) {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID));
|
|
if (!mCacheEntry) {
|
|
aDeviceID.Truncate();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
aDeviceID.Assign(mCacheEntry->GetDeviceID());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetKey(nsACString& result) {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETKEY));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetFetchCount(int32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->FetchCount();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetLastFetched(uint32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->LastFetched();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetLastModified(uint32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTMODIFIED));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->LastModified();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetExpirationTime(uint32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETEXPIRATIONTIME));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->ExpirationTime();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetExpirationTime(uint32_t expirationTime) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETEXPIRATIONTIME));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mCacheEntry->SetExpirationTime(expirationTime);
|
|
mCacheEntry->MarkEntryDirty();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(bool* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_ISSTREAMBASED));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->IsStreamData();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(int64_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->PredictedDataSize();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(
|
|
int64_t predictedSize) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mCacheEntry->SetPredictedDataSize(predictedSize);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(uint32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDATASIZE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
const char* val = mCacheEntry->GetMetaDataElement("uncompressed-len");
|
|
if (!val) {
|
|
*result = mCacheEntry->DataSize();
|
|
} else {
|
|
*result = atol(val);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::GetStorageDataSize(uint32_t* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEDATASIZE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->DataSize();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::RequestDataSizeChange(int32_t deltaSize) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_REQUESTDATASIZECHANGE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsresult rv;
|
|
rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// XXX review for signed/unsigned math errors
|
|
uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
|
|
mCacheEntry->SetDataSize(newDataSize);
|
|
mCacheEntry->TouchData();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetDataSize(uint32_t dataSize) {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETDATASIZE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// XXX review for signed/unsigned math errors
|
|
int32_t deltaSize = dataSize - mCacheEntry->DataSize();
|
|
|
|
nsresult rv;
|
|
rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
|
|
// this had better be NS_OK, this call instance is advisory for memory cache
|
|
// objects
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// XXX review for signed/unsigned math errors
|
|
uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
|
|
mCacheEntry->SetDataSize(newDataSize);
|
|
mCacheEntry->TouchData();
|
|
} else {
|
|
NS_WARNING("failed SetDataSize() on memory cache object!");
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::OpenInputStream(uint32_t offset,
|
|
nsIInputStream** result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
|
|
RefPtr<nsInputStreamWrapper> cacheInput;
|
|
{
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
|
|
|
|
// Don't open any new stream when closing descriptor or clearing entries
|
|
if (mClosingDescriptor || nsCacheService::GetClearingEntries())
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// ensure valid permissions
|
|
if (!(mAccessGranted & nsICache::ACCESS_READ))
|
|
return NS_ERROR_CACHE_READ_ACCESS_DENIED;
|
|
|
|
const char* val;
|
|
val = mCacheEntry->GetMetaDataElement("uncompressed-len");
|
|
if (val) {
|
|
cacheInput = new nsDecompressInputStreamWrapper(this, offset);
|
|
} else {
|
|
cacheInput = new nsInputStreamWrapper(this, offset);
|
|
}
|
|
if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
mInputWrappers.AppendElement(cacheInput);
|
|
}
|
|
|
|
cacheInput.forget(result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset,
|
|
nsIOutputStream** result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
|
|
RefPtr<nsOutputStreamWrapper> cacheOutput;
|
|
{
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
|
|
|
|
// Don't open any new stream when closing descriptor or clearing entries
|
|
if (mClosingDescriptor || nsCacheService::GetClearingEntries())
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// ensure valid permissions
|
|
if (!(mAccessGranted & nsICache::ACCESS_WRITE))
|
|
return NS_ERROR_CACHE_WRITE_ACCESS_DENIED;
|
|
|
|
int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
|
|
const char* val;
|
|
val = mCacheEntry->GetMetaDataElement("uncompressed-len");
|
|
if ((compressionLevel > 0) && val) {
|
|
cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
|
|
} else {
|
|
// clear compression flag when compression disabled - see bug 715198
|
|
if (val) {
|
|
mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
|
|
}
|
|
cacheOutput = new nsOutputStreamWrapper(this, offset);
|
|
}
|
|
if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
mOutputWrapper = cacheOutput;
|
|
}
|
|
|
|
cacheOutput.forget(result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetCacheElement(nsISupports** result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCACHEELEMENT));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
|
|
|
|
NS_IF_ADDREF(*result = mCacheEntry->Data());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetCacheElement(nsISupports* cacheElement) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETCACHEELEMENT));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
|
|
|
|
return nsCacheService::SetCacheElement(mCacheEntry, cacheElement);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
*result = mAccessGranted;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy* result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEPOLICY));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->StoragePolicy();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSTORAGEPOLICY));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
// XXX validate policy against session?
|
|
|
|
bool storageEnabled = false;
|
|
storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy);
|
|
if (!storageEnabled) return NS_ERROR_FAILURE;
|
|
|
|
// Don't change the storage policy of entries we can't write
|
|
if (!(mAccessGranted & nsICache::ACCESS_WRITE)) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// Don't allow a cache entry to move from memory-only to anything else
|
|
if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY &&
|
|
policy != nsICache::STORE_IN_MEMORY)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mCacheEntry->SetStoragePolicy(policy);
|
|
mCacheEntry->MarkEntryDirty();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetFile(nsIFile** result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFILE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return nsCacheService::GetFileForEntry(mCacheEntry, result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetSecurityInfo(nsISupports** result) {
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSECURITYINFO));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = mCacheEntry->SecurityInfo();
|
|
NS_IF_ADDREF(*result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetSecurityInfo(nsISupports* securityInfo) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSECURITYINFO));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mCacheEntry->SetSecurityInfo(securityInfo);
|
|
mCacheEntry->MarkEntryDirty();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::Doom() {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOM));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return nsCacheService::DoomEntry(mCacheEntry);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status) {
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOMANDFAILPENDINGREQUESTS));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::AsyncDoom(nsICacheListener* listener) {
|
|
bool asyncDoomPending;
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
asyncDoomPending = mAsyncDoomPending;
|
|
mAsyncDoomPending = true;
|
|
}
|
|
|
|
if (asyncDoomPending) {
|
|
// AsyncDoom was already called. Notify listener if it is non-null,
|
|
// otherwise just return success.
|
|
if (listener) {
|
|
nsresult rv = NS_DispatchToCurrentThread(
|
|
new nsNotifyDoomListener(listener, NS_ERROR_NOT_AVAILABLE));
|
|
if (NS_SUCCEEDED(rv)) NS_IF_ADDREF(listener);
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> event = new nsAsyncDoomEvent(this, listener);
|
|
return nsCacheService::DispatchToCacheIOThread(event);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::MarkValid() {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_MARKVALID));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsresult rv = nsCacheService::ValidateEntry(mCacheEntry);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::Close() {
|
|
RefPtr<nsOutputStreamWrapper> outputWrapper;
|
|
nsTArray<RefPtr<nsInputStreamWrapper> > inputWrappers;
|
|
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// Make sure no other stream can be opened
|
|
mClosingDescriptor = true;
|
|
outputWrapper = mOutputWrapper;
|
|
for (size_t i = 0; i < mInputWrappers.Length(); i++)
|
|
inputWrappers.AppendElement(mInputWrappers[i]);
|
|
}
|
|
|
|
// Call Close() on the streams outside the lock since it might need to call
|
|
// methods that grab the cache service lock, e.g. compressed output stream
|
|
// when it finalizes the entry
|
|
if (outputWrapper) {
|
|
if (NS_FAILED(outputWrapper->Close())) {
|
|
NS_WARNING("Dooming entry because Close() failed!!!");
|
|
Doom();
|
|
}
|
|
outputWrapper = nullptr;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < inputWrappers.Length(); i++)
|
|
inputWrappers[i]->Close();
|
|
|
|
inputWrappers.Clear();
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// XXX perhaps closing descriptors should clear/sever transports
|
|
|
|
// tell nsCacheService we're going away
|
|
nsCacheService::CloseDescriptor(this);
|
|
NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::GetMetaDataElement(const char* key, char** result) {
|
|
NS_ENSURE_ARG_POINTER(key);
|
|
*result = nullptr;
|
|
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETMETADATAELEMENT));
|
|
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
const char* value;
|
|
|
|
value = mCacheEntry->GetMetaDataElement(key);
|
|
if (!value) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = NS_xstrdup(value);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::SetMetaDataElement(const char* key, const char* value) {
|
|
NS_ENSURE_ARG_POINTER(key);
|
|
|
|
nsCacheServiceAutoLock lock(
|
|
LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETMETADATAELEMENT));
|
|
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
// XXX allow null value, for clearing key?
|
|
|
|
nsresult rv = mCacheEntry->SetMetaDataElement(key, value);
|
|
if (NS_SUCCEEDED(rv)) mCacheEntry->TouchMetaData();
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor* visitor) {
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_VISITMETADATA));
|
|
// XXX check callers, we're calling out of module
|
|
NS_ENSURE_ARG_POINTER(visitor);
|
|
if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return mCacheEntry->VisitMetaDataElements(visitor);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* nsCacheInputStream - a wrapper for nsIInputStream keeps the cache entry
|
|
* open while referenced.
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsInputStreamWrapper)
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsCacheEntryDescriptor::nsInputStreamWrapper::Release() {
|
|
// Holding a reference to descriptor ensures that cache service won't go
|
|
// away. Do not grab cache service lock if there is no descriptor.
|
|
RefPtr<nsCacheEntryDescriptor> desc;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
desc = mDescriptor;
|
|
}
|
|
|
|
if (desc) nsCacheService::Lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_RELEASE));
|
|
|
|
nsrefcnt count;
|
|
MOZ_ASSERT(0 != mRefCnt, "dup release");
|
|
count = --mRefCnt;
|
|
NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsInputStreamWrapper");
|
|
|
|
if (0 == count) {
|
|
// don't use desc here since mDescriptor might be already nulled out
|
|
if (mDescriptor) {
|
|
NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
|
|
"Wrapper not found in array!");
|
|
mDescriptor->mInputWrappers.RemoveElement(this);
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
mRefCnt = 1;
|
|
delete (this);
|
|
return 0;
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
return count;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsInputStreamWrapper)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInputStream)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::LazyInit() {
|
|
// Check if we have the descriptor. If not we can't even grab the cache
|
|
// lock since it is not ensured that the cache service still exists.
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT));
|
|
|
|
nsCacheAccessMode mode;
|
|
nsresult rv = mDescriptor->GetAccessGranted(&mode);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
|
|
if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode, mStartOffset,
|
|
getter_AddRefs(mInput));
|
|
|
|
CACHE_LOG_DEBUG(
|
|
("nsInputStreamWrapper::LazyInit "
|
|
"[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
|
|
mDescriptor, this, mInput.get(), int(rv)));
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::EnsureInit() {
|
|
if (mInitialized) {
|
|
NS_ASSERTION(mDescriptor, "Bad state");
|
|
return NS_OK;
|
|
}
|
|
|
|
return LazyInit();
|
|
}
|
|
|
|
void nsCacheEntryDescriptor::nsInputStreamWrapper::CloseInternal() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
if (!mDescriptor) {
|
|
NS_ASSERTION(!mInitialized, "Bad state");
|
|
NS_ASSERTION(!mInput, "Bad state");
|
|
return;
|
|
}
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL));
|
|
|
|
if (mDescriptor) {
|
|
mDescriptor->mInputWrappers.RemoveElement(this);
|
|
nsCacheService::ReleaseObject_Locked(mDescriptor);
|
|
mDescriptor = nullptr;
|
|
}
|
|
mInitialized = false;
|
|
mInput = nullptr;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Close() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
return Close_Locked();
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Close_Locked() {
|
|
nsresult rv = EnsureInit();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = mInput->Close();
|
|
} else {
|
|
NS_ASSERTION(!mInput, "Shouldn't have mInput when EnsureInit() failed");
|
|
}
|
|
|
|
// Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
|
|
// closing streams with nsCacheService::CloseAllStream()
|
|
CloseInternal();
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Available(
|
|
uint64_t* avail) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
nsresult rv = EnsureInit();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return mInput->Available(avail);
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Read(
|
|
char* buf, uint32_t count, uint32_t* countRead) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
return Read_Locked(buf, count, countRead);
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Read_Locked(
|
|
char* buf, uint32_t count, uint32_t* countRead) {
|
|
nsresult rv = EnsureInit();
|
|
if (NS_SUCCEEDED(rv)) rv = mInput->Read(buf, count, countRead);
|
|
|
|
CACHE_LOG_DEBUG(
|
|
("nsInputStreamWrapper::Read "
|
|
"[entry=%p, wrapper=%p, mInput=%p, rv=%" PRId32 "]",
|
|
mDescriptor, this, mInput.get(), static_cast<uint32_t>(rv)));
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::ReadSegments(
|
|
nsWriteSegmentFun writer, void* closure, uint32_t count,
|
|
uint32_t* countRead) {
|
|
// cache stream not buffered
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::IsNonBlocking(
|
|
bool* result) {
|
|
// cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
|
|
*result = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* nsDecompressInputStreamWrapper - an input stream wrapper that decompresses
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Release() {
|
|
// Holding a reference to descriptor ensures that cache service won't go
|
|
// away. Do not grab cache service lock if there is no descriptor.
|
|
RefPtr<nsCacheEntryDescriptor> desc;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
desc = mDescriptor;
|
|
}
|
|
|
|
if (desc)
|
|
nsCacheService::Lock(LOCK_TELEM(NSDECOMPRESSINPUTSTREAMWRAPPER_RELEASE));
|
|
|
|
nsrefcnt count;
|
|
MOZ_ASSERT(0 != mRefCnt, "dup release");
|
|
count = --mRefCnt;
|
|
NS_LOG_RELEASE(this, count,
|
|
"nsCacheEntryDescriptor::nsDecompressInputStreamWrapper");
|
|
|
|
if (0 == count) {
|
|
// don't use desc here since mDescriptor might be already nulled out
|
|
if (mDescriptor) {
|
|
NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
|
|
"Wrapper not found in array!");
|
|
mDescriptor->mInputWrappers.RemoveElement(this);
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
mRefCnt = 1;
|
|
delete (this);
|
|
return 0;
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
return count;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInputStream)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Read(
|
|
char* buf, uint32_t count, uint32_t* countRead) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
int zerr = Z_OK;
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!mStreamInitialized) {
|
|
rv = InitZstream();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mZstream.next_out = (Bytef*)buf;
|
|
mZstream.avail_out = count;
|
|
|
|
if (mReadBufferLen < count) {
|
|
// Allocate a buffer for reading from the input stream. This will
|
|
// determine the max number of compressed bytes read from the
|
|
// input stream at one time. Making the buffer size proportional
|
|
// to the request size is not necessary, but helps minimize the
|
|
// number of read requests to the input stream.
|
|
uint32_t newBufLen = std::max(count, (uint32_t)kMinDecompressReadBufLen);
|
|
mReadBuffer = (unsigned char*)moz_xrealloc(mReadBuffer, newBufLen);
|
|
mReadBufferLen = newBufLen;
|
|
if (!mReadBuffer) {
|
|
mReadBufferLen = 0;
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
// read and inflate data until the output buffer is full, or
|
|
// there is no more data to read
|
|
while (NS_SUCCEEDED(rv) && zerr == Z_OK && mZstream.avail_out > 0 &&
|
|
count > 0) {
|
|
if (mZstream.avail_in == 0) {
|
|
rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer, mReadBufferLen,
|
|
&mZstream.avail_in);
|
|
if (NS_FAILED(rv) || !mZstream.avail_in) {
|
|
break;
|
|
}
|
|
mZstream.next_in = mReadBuffer;
|
|
}
|
|
zerr = inflate(&mZstream, Z_NO_FLUSH);
|
|
if (zerr == Z_STREAM_END) {
|
|
// The compressed data may have been stored in multiple
|
|
// chunks/streams. To allow for this case, re-initialize
|
|
// the inflate stream and continue decompressing from
|
|
// the next byte.
|
|
Bytef* saveNextIn = mZstream.next_in;
|
|
unsigned int saveAvailIn = mZstream.avail_in;
|
|
Bytef* saveNextOut = mZstream.next_out;
|
|
unsigned int saveAvailOut = mZstream.avail_out;
|
|
inflateReset(&mZstream);
|
|
mZstream.next_in = saveNextIn;
|
|
mZstream.avail_in = saveAvailIn;
|
|
mZstream.next_out = saveNextOut;
|
|
mZstream.avail_out = saveAvailOut;
|
|
zerr = Z_OK;
|
|
} else if (zerr != Z_OK) {
|
|
rv = NS_ERROR_INVALID_CONTENT_ENCODING;
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(rv)) {
|
|
*countRead = count - mZstream.avail_out;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Close() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
EndZstream();
|
|
if (mReadBuffer) {
|
|
free(mReadBuffer);
|
|
mReadBuffer = nullptr;
|
|
mReadBufferLen = 0;
|
|
}
|
|
return nsInputStreamWrapper::Close_Locked();
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::InitZstream() {
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (mStreamEnded) return NS_ERROR_FAILURE;
|
|
|
|
// Initialize zlib inflate stream
|
|
mZstream.zalloc = Z_NULL;
|
|
mZstream.zfree = Z_NULL;
|
|
mZstream.opaque = Z_NULL;
|
|
mZstream.next_out = Z_NULL;
|
|
mZstream.avail_out = 0;
|
|
mZstream.next_in = Z_NULL;
|
|
mZstream.avail_in = 0;
|
|
if (inflateInit(&mZstream) != Z_OK) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mStreamInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::EndZstream() {
|
|
if (mStreamInitialized && !mStreamEnded) {
|
|
inflateEnd(&mZstream);
|
|
mStreamInitialized = false;
|
|
mStreamEnded = true;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of
|
|
* data written to a cache entry.
|
|
* - also keeps the cache entry open while referenced.
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsOutputStreamWrapper)
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsCacheEntryDescriptor::nsOutputStreamWrapper::Release() {
|
|
// Holding a reference to descriptor ensures that cache service won't go
|
|
// away. Do not grab cache service lock if there is no descriptor.
|
|
RefPtr<nsCacheEntryDescriptor> desc;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
desc = mDescriptor;
|
|
}
|
|
|
|
if (desc) nsCacheService::Lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_RELEASE));
|
|
|
|
nsrefcnt count;
|
|
MOZ_ASSERT(0 != mRefCnt, "dup release");
|
|
count = --mRefCnt;
|
|
NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsOutputStreamWrapper");
|
|
|
|
if (0 == count) {
|
|
// don't use desc here since mDescriptor might be already nulled out
|
|
if (mDescriptor) mDescriptor->mOutputWrapper = nullptr;
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
mRefCnt = 1;
|
|
delete (this);
|
|
return 0;
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
return count;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsOutputStreamWrapper)
|
|
NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::LazyInit() {
|
|
// Check if we have the descriptor. If not we can't even grab the cache
|
|
// lock since it is not ensured that the cache service still exists.
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT));
|
|
|
|
nsCacheAccessMode mode;
|
|
nsresult rv = mDescriptor->GetAccessGranted(&mode);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
|
|
if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
NS_ASSERTION(mOutput == nullptr, "mOutput set in LazyInit");
|
|
|
|
nsCOMPtr<nsIOutputStream> stream;
|
|
rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset,
|
|
getter_AddRefs(stream));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCacheDevice* device = cacheEntry->CacheDevice();
|
|
if (device) {
|
|
// the entry has been truncated to mStartOffset bytes, inform device
|
|
int32_t size = cacheEntry->DataSize();
|
|
rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size);
|
|
if (NS_SUCCEEDED(rv)) cacheEntry->SetDataSize(mStartOffset);
|
|
} else {
|
|
rv = NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// If anything above failed, clean up internal state and get out of here
|
|
// (see bug #654926)...
|
|
if (NS_FAILED(rv)) {
|
|
nsCacheService::ReleaseObject_Locked(stream.forget().take());
|
|
mDescriptor->mOutputWrapper = nullptr;
|
|
nsCacheService::ReleaseObject_Locked(mDescriptor);
|
|
mDescriptor = nullptr;
|
|
mInitialized = false;
|
|
return rv;
|
|
}
|
|
|
|
mOutput = stream;
|
|
mInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::EnsureInit() {
|
|
if (mInitialized) {
|
|
NS_ASSERTION(mDescriptor, "Bad state");
|
|
return NS_OK;
|
|
}
|
|
|
|
return LazyInit();
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::OnWrite(
|
|
uint32_t count) {
|
|
if (count > INT32_MAX) return NS_ERROR_UNEXPECTED;
|
|
return mDescriptor->RequestDataSizeChange((int32_t)count);
|
|
}
|
|
|
|
void nsCacheEntryDescriptor::nsOutputStreamWrapper::CloseInternal() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
if (!mDescriptor) {
|
|
NS_ASSERTION(!mInitialized, "Bad state");
|
|
NS_ASSERTION(!mOutput, "Bad state");
|
|
return;
|
|
}
|
|
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL));
|
|
|
|
if (mDescriptor) {
|
|
mDescriptor->mOutputWrapper = nullptr;
|
|
nsCacheService::ReleaseObject_Locked(mDescriptor);
|
|
mDescriptor = nullptr;
|
|
}
|
|
mInitialized = false;
|
|
mOutput = nullptr;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Close() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
return Close_Locked();
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::Close_Locked() {
|
|
nsresult rv = EnsureInit();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = mOutput->Close();
|
|
} else {
|
|
NS_ASSERTION(!mOutput, "Shouldn't have mOutput when EnsureInit() failed");
|
|
}
|
|
|
|
// Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
|
|
// closing streams with nsCacheService::CloseAllStream()
|
|
CloseInternal();
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Flush() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
nsresult rv = EnsureInit();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return mOutput->Flush();
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Write(
|
|
const char* buf, uint32_t count, uint32_t* result) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
return Write_Locked(buf, count, result);
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
|
|
const char* buf, uint32_t count, uint32_t* result) {
|
|
nsresult rv = EnsureInit();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = OnWrite(count);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return mOutput->Write(buf, count, result);
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::WriteFrom(
|
|
nsIInputStream* inStr, uint32_t count, uint32_t* result) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::WriteSegments(
|
|
nsReadSegmentFun reader, void* closure, uint32_t count, uint32_t* result) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::IsNonBlocking(
|
|
bool* result) {
|
|
// cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
|
|
*result = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* nsCompressOutputStreamWrapper - an output stream wrapper that compresses
|
|
* data before it is written
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Release() {
|
|
// Holding a reference to descriptor ensures that cache service won't go
|
|
// away. Do not grab cache service lock if there is no descriptor.
|
|
RefPtr<nsCacheEntryDescriptor> desc;
|
|
|
|
{
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
desc = mDescriptor;
|
|
}
|
|
|
|
if (desc)
|
|
nsCacheService::Lock(LOCK_TELEM(NSCOMPRESSOUTPUTSTREAMWRAPPER_RELEASE));
|
|
|
|
nsrefcnt count;
|
|
MOZ_ASSERT(0 != mRefCnt, "dup release");
|
|
count = --mRefCnt;
|
|
NS_LOG_RELEASE(this, count,
|
|
"nsCacheEntryDescriptor::nsCompressOutputStreamWrapper");
|
|
|
|
if (0 == count) {
|
|
// don't use desc here since mDescriptor might be already nulled out
|
|
if (mDescriptor) mDescriptor->mOutputWrapper = nullptr;
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
mRefCnt = 1;
|
|
delete (this);
|
|
return 0;
|
|
}
|
|
|
|
if (desc) nsCacheService::Unlock();
|
|
|
|
return count;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
|
|
NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Write(
|
|
const char* buf, uint32_t count, uint32_t* result) {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
int zerr = Z_OK;
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!mStreamInitialized) {
|
|
rv = InitZstream();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (!mWriteBuffer) {
|
|
// Once allocated, this buffer is referenced by the zlib stream and
|
|
// cannot be grown. We use 2x(initial write request) to approximate
|
|
// a stream buffer size proportional to request buffers.
|
|
mWriteBufferLen = std::max(count * 2, (uint32_t)kMinCompressWriteBufLen);
|
|
mWriteBuffer = (unsigned char*)moz_xmalloc(mWriteBufferLen);
|
|
mZstream.next_out = mWriteBuffer;
|
|
mZstream.avail_out = mWriteBufferLen;
|
|
}
|
|
|
|
// Compress (deflate) the requested buffer. Keep going
|
|
// until the entire buffer has been deflated.
|
|
mZstream.avail_in = count;
|
|
mZstream.next_in = (Bytef*)buf;
|
|
while (mZstream.avail_in > 0) {
|
|
zerr = deflate(&mZstream, Z_NO_FLUSH);
|
|
if (zerr == Z_STREAM_ERROR) {
|
|
deflateEnd(&mZstream);
|
|
mStreamEnded = true;
|
|
mStreamInitialized = false;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// Note: Z_BUF_ERROR is non-fatal and sometimes expected here.
|
|
|
|
// If the compression stream output buffer is filled, write
|
|
// it out to the underlying stream wrapper.
|
|
if (mZstream.avail_out == 0) {
|
|
rv = WriteBuffer();
|
|
if (NS_FAILED(rv)) {
|
|
deflateEnd(&mZstream);
|
|
mStreamEnded = true;
|
|
mStreamInitialized = false;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
*result = count;
|
|
mUncompressedCount += *result;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Close() {
|
|
mozilla::MutexAutoLock lock(mLock);
|
|
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsresult retval = NS_OK;
|
|
nsresult rv;
|
|
int zerr = 0;
|
|
|
|
if (mStreamInitialized) {
|
|
// complete compression of any data remaining in the zlib stream
|
|
do {
|
|
zerr = deflate(&mZstream, Z_FINISH);
|
|
rv = WriteBuffer();
|
|
if (NS_FAILED(rv)) retval = rv;
|
|
} while (zerr == Z_OK && rv == NS_OK);
|
|
deflateEnd(&mZstream);
|
|
mStreamInitialized = false;
|
|
}
|
|
// Do not allow to initialize stream after calling Close().
|
|
mStreamEnded = true;
|
|
|
|
if (mDescriptor->CacheEntry()) {
|
|
nsAutoCString uncompressedLenStr;
|
|
rv = mDescriptor->GetMetaDataElement("uncompressed-len",
|
|
getter_Copies(uncompressedLenStr));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
int32_t oldCount = uncompressedLenStr.ToInteger(&rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mUncompressedCount += oldCount;
|
|
}
|
|
}
|
|
uncompressedLenStr.Adopt(nullptr);
|
|
uncompressedLenStr.AppendInt(mUncompressedCount);
|
|
rv = mDescriptor->SetMetaDataElement("uncompressed-len",
|
|
uncompressedLenStr.get());
|
|
if (NS_FAILED(rv)) retval = rv;
|
|
}
|
|
|
|
if (mWriteBuffer) {
|
|
free(mWriteBuffer);
|
|
mWriteBuffer = nullptr;
|
|
mWriteBufferLen = 0;
|
|
}
|
|
|
|
rv = nsOutputStreamWrapper::Close_Locked();
|
|
if (NS_FAILED(rv)) retval = rv;
|
|
|
|
return retval;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::InitZstream() {
|
|
if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (mStreamEnded) return NS_ERROR_FAILURE;
|
|
|
|
// Determine compression level: Aggressive compression
|
|
// may impact performance on mobile devices, while a
|
|
// lower compression level still provides substantial
|
|
// space savings for many text streams.
|
|
int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
|
|
|
|
// Initialize zlib deflate stream
|
|
mZstream.zalloc = Z_NULL;
|
|
mZstream.zfree = Z_NULL;
|
|
mZstream.opaque = Z_NULL;
|
|
if (deflateInit2(&mZstream, compressionLevel, Z_DEFLATED, MAX_WBITS, 8,
|
|
Z_DEFAULT_STRATEGY) != Z_OK) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mZstream.next_in = Z_NULL;
|
|
mZstream.avail_in = 0;
|
|
|
|
mStreamInitialized = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::WriteBuffer() {
|
|
uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out;
|
|
uint32_t result = 0;
|
|
nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
|
|
(const char*)mWriteBuffer, bytesToWrite, &result);
|
|
mZstream.next_out = mWriteBuffer;
|
|
mZstream.avail_out = mWriteBufferLen;
|
|
return rv;
|
|
}
|