/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is nsCacheEntryDescriptor.cpp, released * February 22, 2001. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Gordon Sheridan, 22-February-2001 * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsICache.h" #include "nsCache.h" #include "nsCacheService.h" #include "nsCacheEntryDescriptor.h" #include "nsCacheEntry.h" #include "nsReadableUtils.h" #include "nsIOutputStream.h" #include "nsCRT.h" NS_IMPL_THREADSAFE_ISUPPORTS2(nsCacheEntryDescriptor, nsICacheEntryDescriptor, nsICacheEntryInfo) nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry, nsCacheAccessMode accessGranted) : mCacheEntry(entry), mAccessGranted(accessGranted) { PR_INIT_CLIST(this); NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor } 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(); nsCacheService * service = nsCacheService::GlobalInstance(); NS_RELEASE(service); } NS_IMETHODIMP nsCacheEntryDescriptor::GetClientID(char ** result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return ClientIDFromCacheKey(*(mCacheEntry->Key()), result); } NS_IMETHODIMP nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID) { NS_ENSURE_ARG_POINTER(aDeviceID); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; const char* deviceID = mCacheEntry->GetDeviceID(); if (!deviceID) { *aDeviceID = nsnull; return NS_OK; } *aDeviceID = NS_strdup(deviceID); return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } NS_IMETHODIMP nsCacheEntryDescriptor::GetKey(nsACString &result) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result); } NS_IMETHODIMP nsCacheEntryDescriptor::GetFetchCount(PRInt32 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->FetchCount(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetLastFetched(PRUint32 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->LastFetched(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetLastModified(PRUint32 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->LastModified(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetExpirationTime(PRUint32 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->ExpirationTime(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::SetExpirationTime(PRUint32 expirationTime) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; mCacheEntry->SetExpirationTime(expirationTime); mCacheEntry->MarkEntryDirty(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(PRBool *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->IsStreamData(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(PRInt64 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->PredictedDataSize(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(PRInt64 predictedSize) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; mCacheEntry->SetPredictedDataSize(predictedSize); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(PRUint32 *result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->DataSize(); return NS_OK; } nsresult nsCacheEntryDescriptor::RequestDataSizeChange(PRInt32 deltaSize) { nsCacheServiceAutoLock lock; 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 PRUint32 newDataSize = mCacheEntry->DataSize() + deltaSize; mCacheEntry->SetDataSize(newDataSize); mCacheEntry->TouchData(); } return rv; } NS_IMETHODIMP nsCacheEntryDescriptor::SetDataSize(PRUint32 dataSize) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; // XXX review for signed/unsigned math errors PRInt32 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 PRUint32 newDataSize = mCacheEntry->DataSize() + deltaSize; mCacheEntry->SetDataSize(newDataSize); mCacheEntry->TouchData(); } else { NS_WARNING("failed SetDataSize() on memory cache object!"); } return rv; } NS_IMETHODIMP nsCacheEntryDescriptor::OpenInputStream(PRUint32 offset, nsIInputStream ** result) { NS_ENSURE_ARG_POINTER(result); { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; // ensure valid permissions if (!(mAccessGranted & nsICache::ACCESS_READ)) return NS_ERROR_CACHE_READ_ACCESS_DENIED; } nsInputStreamWrapper* cacheInput = new nsInputStreamWrapper(this, offset); if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*result = cacheInput); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::OpenOutputStream(PRUint32 offset, nsIOutputStream ** result) { NS_ENSURE_ARG_POINTER(result); { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; // ensure valid permissions if (!(mAccessGranted & nsICache::ACCESS_WRITE)) return NS_ERROR_CACHE_WRITE_ACCESS_DENIED; } nsOutputStreamWrapper* cacheOutput = new nsOutputStreamWrapper(this, offset); if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*result = cacheOutput); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result) { NS_ENSURE_ARG_POINTER(result); nsCacheServiceAutoLock lock; 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; 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; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->StoragePolicy(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; // XXX validate policy against session? PRBool storageEnabled = PR_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; 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; 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; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; mCacheEntry->SetSecurityInfo(securityInfo); mCacheEntry->MarkEntryDirty(); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::Doom() { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return nsCacheService::DoomEntry(mCacheEntry); } NS_IMETHODIMP nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status) { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsCacheEntryDescriptor::MarkValid() { nsCacheServiceAutoLock lock; if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; nsresult rv = nsCacheService::ValidateEntry(mCacheEntry); return rv; } NS_IMETHODIMP nsCacheEntryDescriptor::Close() { nsCacheServiceAutoLock lock; 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 == nsnull, "mCacheEntry not null"); return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::GetMetaDataElement(const char *key, char **result) { NS_ENSURE_ARG_POINTER(key); *result = nsnull; nsCacheServiceAutoLock lock; NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE); const char *value; value = mCacheEntry->GetMetaDataElement(key); if (!value) return NS_ERROR_NOT_AVAILABLE; *result = NS_strdup(value); if (!*result) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } NS_IMETHODIMP nsCacheEntryDescriptor::SetMetaDataElement(const char *key, const char *value) { NS_ENSURE_ARG_POINTER(key); nsCacheServiceAutoLock lock; 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; // 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_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsInputStreamWrapper, nsIInputStream) nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::LazyInit() { nsCacheServiceAutoLock lock; 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)); if (NS_FAILED(rv)) return rv; mInitialized = PR_TRUE; return NS_OK; } nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::Close() { nsresult rv = EnsureInit(); if (NS_FAILED(rv)) return rv; return mInput->Close(); } nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::Available(PRUint32 *avail) { nsresult rv = EnsureInit(); if (NS_FAILED(rv)) return rv; return mInput->Available(avail); } nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::Read(char *buf, PRUint32 count, PRUint32 *countRead) { nsresult rv = EnsureInit(); if (NS_FAILED(rv)) return rv; return mInput->Read(buf, count, countRead); } nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure, PRUint32 count, PRUint32 *countRead) { // cache stream not buffered return NS_ERROR_NOT_IMPLEMENTED; } nsresult nsCacheEntryDescriptor:: nsInputStreamWrapper::IsNonBlocking(PRBool *result) { // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK *result = PR_FALSE; return NS_OK; } /****************************************************************************** * nsCacheOutputStream - 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_THREADSAFE_ISUPPORTS1(nsCacheEntryDescriptor::nsOutputStreamWrapper, nsIOutputStream) nsresult nsCacheEntryDescriptor:: nsOutputStreamWrapper::LazyInit() { nsCacheServiceAutoLock lock; 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; rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset, getter_AddRefs(mOutput)); if (NS_FAILED(rv)) return rv; nsCacheDevice* device = cacheEntry->CacheDevice(); if (!device) return NS_ERROR_NOT_AVAILABLE; // the entry has been truncated to mStartOffset bytes, inform the device. PRInt32 size = cacheEntry->DataSize(); rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size); if (NS_FAILED(rv)) return rv; cacheEntry->SetDataSize(mStartOffset); mInitialized = PR_TRUE; return NS_OK; } nsresult nsCacheEntryDescriptor:: nsOutputStreamWrapper::OnWrite(PRUint32 count) { if (count > PR_INT32_MAX) return NS_ERROR_UNEXPECTED; return mDescriptor->RequestDataSizeChange((PRInt32)count); } NS_IMETHODIMP nsCacheEntryDescriptor:: nsOutputStreamWrapper::Close() { nsresult rv = EnsureInit(); if (NS_FAILED(rv)) return rv; return mOutput->Close(); } NS_IMETHODIMP nsCacheEntryDescriptor:: nsOutputStreamWrapper::Flush() { nsresult rv = EnsureInit(); if (NS_FAILED(rv)) return rv; return mOutput->Flush(); } NS_IMETHODIMP nsCacheEntryDescriptor:: nsOutputStreamWrapper::Write(const char * buf, PRUint32 count, PRUint32 * 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, PRUint32 count, PRUint32 * result) { NS_NOTREACHED("cache stream not buffered"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsCacheEntryDescriptor:: nsOutputStreamWrapper::WriteSegments(nsReadSegmentFun reader, void * closure, PRUint32 count, PRUint32 * result) { NS_NOTREACHED("cache stream not buffered"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsCacheEntryDescriptor:: nsOutputStreamWrapper::IsNonBlocking(PRBool *result) { // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK *result = PR_FALSE; return NS_OK; }