/*
* Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Implementation of CHttpCacheHandler
*
*/
// INCLUDE FILES
#include "HttpCacheHandler.h"
#include "HttpCacheManager.h"
#include "HttpCacheEntry.h"
#include "HttpCacheLookupTable.h"
#include "HttpCacheStreamHandler.h"
#include "HttpCacheUtil.h"
#include "HttpCacheEvictionHandler.h"
#include "HttpCacheObserver.h"
#include <http/RHTTPTransaction.h>
#include <http/rhttpsession.h>
#include <http/rhttpheaders.h>
#include <HttpStringConstants.h>
#include <httperr.h>
#include <s32file.h>
#include <BrCtlDefs.h>
// EXTERNAL DATA STRUCTURES
// EXTERNAL FUNCTION PROTOTYPES
// CONSTANTS
// MACROS
// LOCAL CONSTANTS AND MACROS
// MODULE DATA STRUCTURES
// LOCAL FUNCTION PROTOTYPES
// FORWARD DECLARATIONS
// ============================= LOCAL FUNCTIONS ===============================
void PanicCacheHandler(
TInt aError)
{
_LIT(KCachePanic, "cacheHandler Panic");
User::Panic( KCachePanic, aError );
}
// ============================ MEMBER FUNCTIONS ===============================
// -----------------------------------------------------------------------------
// CHttpCacheHandler::CHttpCacheHandler
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CHttpCacheHandler::CHttpCacheHandler(
TInt aSize ) : iSize( aSize )
{
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::ConstructL(
const TDesC& aDirectory,
const TDesC& aIndexFile,
TInt aCriticalLevel)
{
User::LeaveIfError(iRfs.Connect());
//
iIndexFile = aIndexFile.AllocL();
//
iDirectory = aDirectory.AllocL();
//
iEvictionHandler = CHttpCacheEvictionHandler::NewL();
//
iStreamHandler = CHttpCacheStreamHandler::NewL( iDirectory->Des(), aCriticalLevel );
//
iLookupTable = CHttpCacheLookupTable::NewL( *iEvictionHandler, *iStreamHandler );
//1. Set up top-level cache directory if it doesn't exist.
TEntry entry;
TInt err( KErrNone );
if (iRfs.Entry(iDirectory->Des(), entry) != KErrNone)
{
err = iRfs.MkDirAll(iDirectory->Des());
}
//2. Create subdirectories to store header/body files
if ( err == KErrNone || err == KErrAlreadyExists ) { //harmless errors
__ASSERT_DEBUG ( (iDirectory->Des().LocateReverse( KPathDelimiter ) == (iDirectory->Des().Length() - 1)),
PanicCacheHandler( KErrCorrupt ) ); // We assume that iDirectory is terminated by a forward slash
HBufC* subDir = HBufC::NewL(KMaxPath); // Base cache dir + subdir name + another delimiter
_LIT(KFormat,"%S%x%c");
for (TUint i = 0; i < KCacheSubdirCount; i++)
{
TPtrC ptr (iDirectory->Des());
subDir->Des().Format(KFormat, &ptr, i, KPathDelimiter);
TInt err2 = iRfs.MkDir(subDir->Des());
__ASSERT_DEBUG ( (err2 == KErrNone || err2 == KErrAlreadyExists), PanicCacheHandler( err2 ) );
}
delete subDir;
//end cache dir + subdir creation
} else {
User::Leave(err);
}
OpenLookupTableL();
//
iHttpCacheObserver = CHttpCacheObserver::NewL(iDirectory, iIndexFile, this);
iHttpCacheObserver->StartObserver();
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CHttpCacheHandler* CHttpCacheHandler::NewL(
TInt aSize,
const TDesC& aDirectory,
const TDesC& aIndexFile,
TInt aCriticalLevel)
{
CHttpCacheHandler* self = new( ELeave ) CHttpCacheHandler( aSize );
CleanupStack::PushL( self );
self->ConstructL( aDirectory, aIndexFile, aCriticalLevel );
CleanupStack::Pop();
return self;
}
// Destructor
CHttpCacheHandler::~CHttpCacheHandler()
{
TRAP_IGNORE( SaveLookupTableL() );
//
delete iHttpCacheObserver;
//
if (iEvictionHandler)
{
iEvictionHandler->RemoveAll();
}
//
delete iLookupTable;
//
delete iEvictionHandler;
//
delete iStreamHandler;
//
delete iDirectory;
//
delete iIndexFile;
//
iRfs.Close();
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::RequestL
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::RequestL(
RHTTPTransaction& aTrans,
TBrCtlDefs::TBrCtlCacheMode aCacheMode,
THttpCacheEntry& aCacheEntry )
{
HttpCacheUtil::WriteUrlToLog( 0, _L( "request item" ), aTrans.Request().URI().UriDes() );
//
TInt status( KErrNotFound );
CHttpCacheEntry* entry = NULL;
// 0. check if we need to check cache at all (protected vs no cache mode)
// 1. check if the url is in the cache
// 2. check if it is complete
// 3. see if it is useable
// use protected item on reload???
// currently not
// and do not use cache for post
if( aCacheMode != TBrCtlDefs::ECacheModeNoCache &&
HttpCacheUtil::MethodFromStr( aTrans.Request().Method(), aTrans.Session().StringPool() ) != EMethodPost )
{
// If the cacheMode is noCache then it must ignore the cached entry.
entry = iLookupTable->Find( aTrans.Request().URI().UriDes() );
// check if the trailing slash is missing
if( !entry )
{
TUriC8 uri = aTrans.Request().URI();
if( uri.Extract( EUriPath ).Length() == 0 )
{
CUri8* fixeduri = CUri8::NewLC( uri );
fixeduri->SetComponentL( _L8("/"), EUriPath );
//
entry = iLookupTable->Find( fixeduri->Uri().UriDes() );
//
CleanupStack::PopAndDestroy(); // fixeduri
}
}
//
if( entry && entry->State() == CHttpCacheEntry::ECacheComplete )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "item is in the cache" ) );
#endif
//
status = CacheNeedsValidationL( *entry, aTrans, aCacheMode ) ? KErrNotReady : KErrNone;
// entry could be invalidated at this point. check for status to make sure
// the entry is still valid
}
// prepare stream for request
if( status == KErrNone )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "prepare item for sending" ) );
#endif
// attach entry to the stream
if( iStreamHandler->AttachL( *entry ) )
{
entry->SetState( CHttpCacheEntry::ECacheRequesting );
entry->Accessed();
}
else
{
// cleanup on the corrupt entry
HandleCorruptEntry( *entry );
entry = NULL;
// item is not in cache
status = KErrNotFound;
}
}
// cleanup
if( status == KErrNone && entry )
{
// save handler and entry so that
// on next call we don't have to start a lookup again
aCacheEntry.iCacheHandler = this;
aCacheEntry.iCacheEntry = entry;
}
else
{
// cleanup response headers
//
// response is not in the cache, so remove all the response header fields
// set by cache handler
RHTTPHeaders responseHeaders = aTrans.Response().GetHeaderCollection();
responseHeaders.RemoveAllFields();
}
}
#ifdef __CACHELOG__
else
{
HttpCacheUtil::WriteLog( 0, _L( "reload: do not use cache" ) );
}
if( status != KErrNone && entry )
{
//
HttpCacheUtil::WriteLog( 0, _L( "item needs validation" ) );
}
else
{
//
HttpCacheUtil::WriteLog( 0, _L( "item is not in the cache" ) );
}
#endif // __CACHELOG__
return status;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::RequestHeadersL
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::RequestHeadersL(
RHTTPTransaction& aTrans,
THttpCacheEntry& aCacheEntry )
{
HttpCacheUtil::WriteUrlToLog( 0, _L( "request http headers" ), aTrans.Request().URI().UriDes() );
//
TInt status( KErrNotFound );
CHttpCacheEntry* entry = aCacheEntry.iCacheEntry;
//
if( entry && entry->State() == CHttpCacheEntry::ECacheRequesting )
{
// response headers should already have all the headers
// as RequestL call adds them all.
// no need to do much here
// get header from file
status = KErrNone;
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "sending http headers" ) );
#endif
}
return status;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::RequestNextChunkL
//
// -----------------------------------------------------------------------------
//
HBufC8* CHttpCacheHandler::RequestNextChunkL(
RHTTPTransaction& aTrans,
TBool& aLastChunk,
THttpCacheEntry& aCacheEntry )
{
(void)aTrans; //suppress compiler and PC-lint warnings
//
HBufC8* bodyStr = NULL;
CHttpCacheEntry* entry = aCacheEntry.iCacheEntry;
//
if( entry && entry->State() == CHttpCacheEntry::ECacheRequesting )
{
// get next chunk
bodyStr = iStreamHandler->NextChunkL( *entry, aLastChunk );
}
return bodyStr;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::RequestClosed
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::RequestClosed(
RHTTPTransaction* aTrans,
THttpCacheEntry& aCacheEntry )
{
if (aTrans)
HttpCacheUtil::WriteUrlToLog( 0, _L( "Request is closed" ), aTrans->Request().URI().UriDes() );
// fine
// make sure transaction is moved to the complete list
CHttpCacheEntry* entry = aCacheEntry.iCacheEntry;
if( entry )
{
// normal close on a request - when the content is loaded from the cache
if( entry->State() == CHttpCacheEntry::ECacheRequesting )
{
entry->SetState( CHttpCacheEntry::ECacheComplete );
iStreamHandler->Detach( *entry );
}
// transaction is closed without being completed
else if( entry->State() == CHttpCacheEntry::ECacheResponding ||
entry->State() == CHttpCacheEntry::ECacheDestroyed )
{
// remove uncompleted/destroyed entry
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry );
entry = NULL;
aCacheEntry.iCacheEntry = NULL;
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "uncompleted entry" ) );
#endif
}
else if( entry->State() == CHttpCacheEntry::ECacheComplete )
{
// normal close on the request - when the contet is saved to the cache
// ResponseComplete has already been called
// check if the stream is released
__ASSERT_DEBUG( !iStreamHandler->Find( *entry ) , PanicCacheHandler( KErrCorrupt ) );
}
else
{
__ASSERT_DEBUG( EFalse , PanicCacheHandler( KErrCorrupt ) );
}
}
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::AdjustResponseTime
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::AdjustResponseTime(RHTTPTransaction& aTrans)
{
RStringF fieldName;
THTTPHdrVal dateValue;
TDateTime date;
TTime now;
TInt err;
TTime serverResponseTime;
RHTTPHeaders respHeaders = aTrans.Response().GetHeaderCollection();
RStringPool strP = aTrans.Session().StringPool();
// Get the current time. All internet dates are GMT
now.UniversalTime();
// Get the date from the headers. Compare this with the response
// GMT time. If the server time is wrong then adjust the time here.
fieldName = strP.StringF( HTTP::EDate, RHTTPSession::GetTable() );
err = respHeaders.GetField( fieldName, 0, dateValue );
if( err == KErrNotFound || dateValue.Type() != THTTPHdrVal::KDateVal )
{
serverResponseTime = 0;
}
else
{
serverResponseTime = TTime( dateValue.DateTime() );
}
// Implies some thing wrong with with origin server time.
if ( now < serverResponseTime )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "Response time is less than GMT time" ) );
#endif
// Remove the field first. It is must otherwise raw field
// data is not modified. The cache data is written from the
// raw field data.
if( respHeaders.RemoveField(fieldName) == KErrNone )
{
dateValue.SetDateTime( now.DateTime() );
TRAP_IGNORE( respHeaders.SetFieldL( fieldName, dateValue ) );
}
}
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::ReceivedResponseHeadersL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::ReceivedResponseHeadersL(
RHTTPTransaction& aTrans,
THttpCacheEntry& aCacheEntry )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteUrlToLog( 0, _L( "received http headers" ), aTrans.Request().URI().UriDes() );
#endif
//
TBool protectedEntry( EFalse );
// check if the item is cacheable
// no item should be bigger than the 1/3 of the cache size
if( HttpCacheUtil::IsCacheable( aTrans, ( iSize / 3 ), protectedEntry ) )
{
// check if the entry is already in the cache
CHttpCacheEntry* entry = iLookupTable->Find( aTrans.Request().URI().UriDes() );
if( entry )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteUrlToLog( 0, _L( "item is already in the cache" ), entry->Url() );
#endif
//
if( entry->State() != CHttpCacheEntry::ECacheComplete )
{
// multiple incoming entries? doh.
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "MULTIPLE REQUEST!!!!!!!!!!!!!!!!!!!!!!!!!" ) );
#endif
// __ASSERT_DEBUG( EFalse, PanicCacheHandler( KErrCorrupt ) );
// ignore this one and the first will proceed.
entry = NULL;
}
}
else
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "create new cache item" ) );
#endif
//Check adjustment of response time is required or not.
AdjustResponseTime( aTrans );
// hash it
entry = iLookupTable->InsertL( aTrans.Request().URI().UriDes() );
if( entry )
{
// protect this entry
if( protectedEntry )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "this item is protected" ) );
#endif
//
entry->SetProtected();
RHTTPHeaders responseHeaders = aTrans.Response().GetHeaderCollection();
RStringPool strP = aTrans.Session().StringPool();
// double expiration time
HttpCacheUtil::AdjustExpirationTimeL( responseHeaders, strP );
}
}
else
{
// no luck with the lookuptable
__ASSERT_DEBUG( EFalse, PanicCacheHandler( KErrCorrupt ) );
}
}
// save headers
if( entry )
{
// attach it to the stream handler
if( iStreamHandler->AttachL( *entry ) )
{
entry->SetState( CHttpCacheEntry::ECacheResponding );
// 1. handle only 304 and 200
// 2. check if either the header or the body ( or both ) need to be updated
// 3. update the headers anyway in case of notmodified (304)
// 4. remove the old body in case of bodyupdate
TInt httpStatus( aTrans.Response().StatusCode() );
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "status code: " ), httpStatus );
#endif
//
TBool ok( EFalse );
if( httpStatus == HTTPStatus::EOk )
{
ok = HandleResponseOkL( *entry, aTrans );
}
else if( httpStatus == HTTPStatus::ENotModified )
{
ok = HandleResponseNotModifiedL( *entry, aTrans );
}
//
// entry could be corrupted at this point
if( ok )
{
// save handler and entry so that
// on next call we don't have to start a lookup again
aCacheEntry.iCacheHandler = this;
aCacheEntry.iCacheEntry = entry;
}
else
{
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry );
entry = NULL;
}
}
else
{
HandleCorruptEntry( *entry );
entry = NULL;
}
}
}
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::ReceivedResponseBodyDataL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::ReceivedResponseBodyDataL(
RHTTPTransaction& aTrans,
MHTTPDataSupplier& aBodyDataSupplier,
THttpCacheEntry& aCacheEntry )
{
HttpCacheUtil::WriteUrlToLog( 0, _L( "received body" ), aTrans.Request().URI().UriDes() );
// 1. check if we are caching this resource
// 2. update the body data
CHttpCacheEntry* entry = aCacheEntry.iCacheEntry;
if( entry && entry->State() == CHttpCacheEntry::ECacheResponding )
{
HBufC8* bodyStr = HttpCacheUtil::BodyToBufferL( aBodyDataSupplier );
if( bodyStr )
{
// erase entry if we are unable to save it (low disk space)
if( !SaveBuffer( *entry, bodyStr->Des(), ETrue ) )
{
// detach it from the stream and erase it
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry );
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "body cannot be saved" ) );
#endif
entry = NULL;
// remove entry
aCacheEntry.iCacheEntry = NULL;
}
#ifdef __CACHELOG__
else
{
HttpCacheUtil::WriteLog( 0, _L( "body is saved" ) );
}
#endif // __CACHELOG__
//
delete bodyStr;
}
}
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::ResponseComplete
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::ResponseComplete(
RHTTPTransaction& aTrans,
THttpCacheEntry& aCacheEntry )
{
HttpCacheUtil::WriteUrlToLog( 0, _L( "response complete" ), aTrans.Request().URI().UriDes() );
// 1. check if we are caching this resource
// 2. mark the entry as complete
CHttpCacheEntry* entry = aCacheEntry.iCacheEntry;
if( entry )
{
if( entry->State() == CHttpCacheEntry::ECacheResponding )
{
// flush the entry
if( !iStreamHandler->Flush( *entry ) )
{
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry );
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "body cannot be saved" ) );
#endif
entry = NULL;
// remove entry
aCacheEntry.iCacheEntry = NULL;
}
else
{
entry->SetState( CHttpCacheEntry::ECacheComplete );
iStreamHandler->Detach( *entry );
}
}
else if( entry->State() == CHttpCacheEntry::ECacheDestroyed )
{
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry, EFalse );
aCacheEntry.iCacheEntry = NULL;
}
}
}
// -----------------------------------------------------------------------------
// Removes all entries in the Cache lookup table, commits table to disk.
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::RemoveAllL()
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "remove all items" ) );
#endif
TInt numberOfBytes;
// clear all the inactive entries
numberOfBytes = iLookupTable->RemoveAll();
// and save it. user initiated. no need to do idle save
SaveLookupTableL();
return numberOfBytes;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::ListFiles
// Adds all filenames known to this Cache to aFilenameList
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::ListFiles(RPointerArray<TDesC>& aFilenameList)
{
return iLookupTable->ListFiles(aFilenameList);
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::RemoveL
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::RemoveL(
const TDesC8& aUrl )
{
TInt status( KErrNotFound );
HttpCacheUtil::WriteUrlToLog( 0, _L( "remove item:" ), aUrl );
CHttpCacheEntry* entry = iLookupTable->Find( aUrl );
if( entry )
{
if( entry->State() == CHttpCacheEntry::ECacheComplete )
{
// delete
status = iLookupTable->Remove( aUrl );
}
else
{
// mark it as deleted and erase it when the
// trans is complete
entry->SetState( CHttpCacheEntry::ECacheDestroyed );
status = KErrNone;
}
}
return status;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::Find
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::Find(
const TDesC8& aUrl )
{
// find
CHttpCacheEntry* entry = iLookupTable->Find( aUrl );
return ( entry ? ( entry->State() == CHttpCacheEntry::ECacheComplete ) : EFalse );
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::SaveL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::SaveL(
const TDesC8& aUrl,
const TDesC8& aHeader,
const TDesC8& aContent )
{
TBool saved( EFalse );
// check if entry exist. do not overwrite.
CHttpCacheEntry* entry = iLookupTable->Find( aUrl );
if( !entry )
{
entry = iLookupTable->InsertL( aUrl );
// prepare for saving
if( entry && iStreamHandler->AttachL( *entry ) )
{
// save header and body
saved = SaveBuffer( *entry, aHeader, EFalse ) && SaveBuffer( *entry, aContent, ETrue );
if( saved )
{
// flush
saved = iStreamHandler->Flush( *entry );
if( saved )
{
entry->SetState( CHttpCacheEntry::ECacheComplete );
}
}
iStreamHandler->Detach( *entry );
}
// cleanup
if( !saved && entry )
{
HandleCorruptEntry( *entry );
}
}
return saved;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::AddHeaderL
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheHandler::AddHeaderL(
const TDesC8& aUrl,
const TDesC8& aName,
const TDesC8& aValue )
{
TInt status( KErrNotFound );
//
CHttpCacheEntry* entry = iLookupTable->Find( aUrl );
if( entry )
{
TBool attached;
//
attached = iStreamHandler->AttachL( *entry );
// get headers
HBufC8* headersStr = iStreamHandler->HeadersL( *entry );
if( headersStr )
{
CleanupStack::PushL( headersStr );
// alter headers and save them
HBufC8* newHeaderStr = HttpCacheUtil::AddHeaderLC( aName, aValue, headersStr->Des() );
if( newHeaderStr )
{
// remove old headers first
iStreamHandler->RemoveHeaders( *entry );
// save new headers
if( !SaveBuffer( *entry, newHeaderStr->Des(), EFalse ) )
{
status = KErrDirFull;
// failed. should we save the original headers?
TBool saveOk( SaveBuffer( *entry, headersStr->Des(), EFalse ) );
// original save should never fail
__ASSERT_DEBUG( saveOk, PanicCacheHandler( KErrCorrupt ) );
if( !saveOk )
{
// sorry, we made this entry corrupt. remove it
iStreamHandler->Detach( *entry );
HandleCorruptEntry( *entry );
entry = NULL;
}
}
else
{
status = KErrNone;
}
CleanupStack::PopAndDestroy(); // newHeaderStr
}
CleanupStack::PopAndDestroy(); // headersStr
}
// detach
if( attached )
{
iStreamHandler->Detach( *entry );
}
}
return status;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::CacheNeedsValidationL
//
// Note: This function check if cache needs validation
// Delete cache entry from this function is too early, should avoid it
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::CacheNeedsValidationL(
CHttpCacheEntry& aCacheEntry,
RHTTPTransaction& aTrans,
TBrCtlDefs::TBrCtlCacheMode aCacheMode )
{
// The entry isn't useable unless otherwise stated
TBool mustRevalidate( ETrue );
// If the cacheMode is PreferCache then it must use it even if it
// is expired, etc. This is typically used during history navigation.
//
// Note: Cache also set the cacheMode to PreferCache after a resource has
// been validated -- that is when it tries to refetch an entry from
// cache after receiving a 304 response.
// get cached headers
if( iStreamHandler->AttachL( aCacheEntry ) )
{
HBufC8* headersStr = iStreamHandler->HeadersL( aCacheEntry );
CleanupStack::PushL( headersStr );
iStreamHandler->Detach( aCacheEntry );
// headersStr == NULL happens if you erase the cache directory
// using a file manager
if( headersStr )
{
// use response headers for retreiving cached headers
RHTTPHeaders responseHeaders = aTrans.Response().GetHeaderCollection();
RHTTPHeaders requestHeaders = aTrans.Request().GetHeaderCollection();
RStringPool strP = aTrans.Session().StringPool();
// convert the buffer to httpHeader
HttpCacheUtil::BufferToHeadersL( headersStr->Des(), responseHeaders, strP );
// check if we need to validate the cahce
if( aCacheMode == TBrCtlDefs::ECacheModeOnlyCache || aCacheMode == TBrCtlDefs::ECacheModeHistory )
{
// no validation required
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "prefer cache mode. no need to revalidate" ), aCacheMode );
#endif
mustRevalidate = EFalse;
}
else
{
// Get the pragma no-cache header from the headers
// no-cache on request header means "do not use cache"
if( !HttpCacheUtil::PragmaNoCache( aTrans ) )
{
if( !HttpCacheUtil::CacheTimeIsFresh( requestHeaders, responseHeaders, strP ) )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "cache item is not fresh. needs revalidation" ) );
#endif
// MKLE-7PRD27: Avoid removing cache entry here
mustRevalidate = ETrue;
// add headers like EIfModifiedSince, EETag, EIfNoneMatch
HttpCacheUtil::AddValidationHeaders( responseHeaders, requestHeaders, strP );
}
else
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "cache item is fresh. needs no revalidation" ) );
#endif
//
mustRevalidate = EFalse;
}
}
else
{
// needs validation
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "no cache/no store header present. need revalidation" ) );
#endif
mustRevalidate = ETrue;
}
}
}
CleanupStack::PopAndDestroy(); // headersStr
}
else
{
HandleCorruptEntry( aCacheEntry );
// needs validation
mustRevalidate = ETrue;
}
return mustRevalidate;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::CacheNeedsSpaceL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::CacheNeedsSpaceL(
TInt aSize )
{
TBool ok( ETrue );
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "the cache is this big" ), iSize );
HttpCacheUtil::WriteLog( 0, _L( "we occupy" ), iStreamHandler->SavedContentSize() );
HttpCacheUtil::WriteLog( 0, _L( "this item needs space:" ), aSize );
#endif // __CACHELOG__
// check if we need extra space
if( iStreamHandler->SavedContentSize() + aSize > iSize )
{
#ifdef __CACHELOG__
// items in the cache
TInt size( 0 );
HttpCacheUtil::WriteLog( 0, _L( "cached items" ) );
const CArrayPtrFlat<CHttpCacheEntry>& entries = iLookupTable->Entries();
for( TInt i = 0; i < entries.Count(); i++ )
{
CHttpCacheEntry* entry = entries.At( i );
if( entry && entry != (CHttpCacheEntry*)0xffffffff )
{
HttpCacheUtil::WriteUrlToLog( 0, entry->Url(), entry->Size() );
size+=entry->Size();
size+=entry->HeaderSize();
}
}
HttpCacheUtil::WriteLog( 0, _L( "occupy with headers:" ), size );
#endif // __CACHELOG__
CArrayPtrFlat<CHttpCacheEntry>* evictedList = iEvictionHandler->EvictL( aSize );
if( evictedList && evictedList->Count() )
{
// destroy items
CHttpCacheEntry* entry;
for( TInt i = 0; i < evictedList->Count(); i++ )
{
//
entry = evictedList->At( i );
if( entry )
{
// destroy
iLookupTable->Remove( entry->Url() );
}
}
// __ASSERT_DEBUG( iStreamHandler->SavedContentSize() + aSize < iSize, PanicCacheHandler( KErrCorrupt ) );
// ok = ETrue if there is enough space
ok = ( iStreamHandler->SavedContentSize() + aSize < iSize );
}
else
{
// interesting...nobody can be evicted...
// they are all protected entries?
// or the incoming -not yet complete- items take the entire cache?
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "NO SPACE can be released!!!" ) );
#endif
ok = EFalse;
}
delete evictedList;
}
return ok;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::HandleResponseOkL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::HandleResponseOkL(
CHttpCacheEntry& aEntry,
RHTTPTransaction& aTrans )
{
TBool saveOk( ETrue );
RHTTPHeaders responseHeader = aTrans.Response().GetHeaderCollection();
RStringPool strP = aTrans.Session().StringPool();
HBufC8* responseHeaderStr = HttpCacheUtil::HeadersToBufferLC( responseHeader, strP );
//
TBool update( ETrue );
// get cached headers to compare
HBufC8* cachedHeaderStr = iStreamHandler->HeadersL( aEntry );
// we've got some headers to update,
// check if we really need to update them
if( cachedHeaderStr )
{
CleanupStack::PushL( cachedHeaderStr );
//
update = HttpCacheUtil::CacheNeedsUpdateL( responseHeader, cachedHeaderStr->Des(), strP );
CleanupStack::PopAndDestroy(); // cachedHeaderStr
}
//
if( update )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "udpate headers" ) );
#endif
if( aEntry.HeaderSize() )
{
// remove it first
iStreamHandler->RemoveHeaders( aEntry );
}
// save
saveOk = SaveBuffer( aEntry, responseHeaderStr->Des() );
if( aEntry.Size() )
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "remove body" ) );
#endif
//
iStreamHandler->RemoveBodyData( aEntry );
}
}
else
{
// if neither the header nor the body need to be updated, then
// detach entry to protect from being updated
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "no udpate needed, ignore response" ) );
#endif
//
aEntry.SetState( CHttpCacheEntry::ECacheComplete );
iStreamHandler->Detach( aEntry );
// pretend that save was ok.
saveOk = ETrue;
}
// destroy corrupt entry by returning EFalse
CleanupStack::PopAndDestroy(); // responseHeaderStr
return saveOk;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::HandleResponseNotModifiedL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::HandleResponseNotModifiedL(
CHttpCacheEntry& aEntry,
RHTTPTransaction& aTrans )
{
// oos? -out of space
TBool saveOk( ETrue );
RHTTPHeaders responseHeader = aTrans.Response().GetHeaderCollection();
RStringPool strP = aTrans.Session().StringPool();
HBufC8* responseHeaderStr = HttpCacheUtil::HeadersToBufferLC( responseHeader, strP );
// If a cache uses a received 304 response to update a cache entry,
// the cache MUST update the entry to reflect any new field values given in the response.
HBufC8* mergedHeadersStr = NULL;
HBufC8* cachedHeaderStr = iStreamHandler->HeadersL( aEntry );
CleanupStack::PushL( cachedHeaderStr );
// don't merge with empty headers
if( cachedHeaderStr )
{
mergedHeadersStr = HttpCacheUtil::MergeHeadersLC( cachedHeaderStr->Des(), responseHeader, strP );
CleanupStack::Pop(); // mergedHeadersStr
}
// don't update empty headers
if( mergedHeadersStr || responseHeaderStr )
{
// remove cached headers first
iStreamHandler->RemoveHeaders( aEntry );
// save merged headers (reponse + cached)
if( mergedHeadersStr )
{
saveOk = SaveBuffer( aEntry, mergedHeadersStr->Des() );
}
else if( responseHeaderStr )
{
// save responseheader instead
saveOk = SaveBuffer( aEntry, responseHeaderStr->Des() );
}
// if save failed, let's see if we can save old
// headers
if( !saveOk && cachedHeaderStr )
{
saveOk = SaveBuffer( aEntry, cachedHeaderStr->Des() );
}
}
// do not remove the body as it was not modified
delete mergedHeadersStr;
CleanupStack::PopAndDestroy( 2 ); // cachedHeaderStr, responseHeaderStr
// check if save was ok.
// or nothing was not saved at all
if( saveOk )
{
// this item does not need update
aEntry.SetState( CHttpCacheEntry::ECacheComplete );
iStreamHandler->Detach( aEntry );
}
return saveOk;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::OpenLookupTableL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::OpenLookupTableL()
{
OpenLookupTableL(iLookupTable);
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::OpenLookupTableL
// Opens the index*.dat lookup table from file system.
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::OpenLookupTableL(CHttpCacheLookupTable* aLookupTable)
{
// read entries from index.dat
RFileReadStream readStream;
iRfs.SetSessionPath( iDirectory->Des() );
TInt ret = KErrNone;
TInt tryCount = 0;
for (tryCount = 0; tryCount < 5; tryCount++)
{
ret = readStream.Open( iRfs, iIndexFile->Des(), EFileRead | EFileShareAny );
if (ret == KErrInUse)
{
// When the cache is full, it takes 65 - 85 miliseconds to write the index.
// So wait 50 miliseconds and try again
User::After(50000);
}
else
{
break;
}
}
if( ret == KErrNone )
{
CleanupClosePushL( readStream );
aLookupTable->InternalizeL( readStream, iDirectory->Des() );
CleanupStack::PopAndDestroy(1); // readStream
}
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::SaveLookupTableL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::SaveLookupTableL()
{
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "lookup table is saved" ) );
#endif
// save entries to index.dat
RFileWriteStream writeStream;
// Don't get notified about own changes
iHttpCacheObserver->Cancel();
TInt ret = KErrNone;
TInt tryCount = 0;
for (tryCount = 0; tryCount < 5; tryCount++)
{
ret = writeStream.Replace( iRfs, iIndexFile->Des(), EFileWrite );
if (ret == KErrInUse)
{
// When the cache is full, it takes 65 - 85 miliseconds to write the index.
// So wait 50 miliseconds and try again
User::After(50000);
}
else
{
break;
}
}
if( ret == KErrNone )
{
CleanupClosePushL( writeStream );
iLookupTable->ExternalizeL( writeStream );
writeStream.CommitL();
CleanupStack::PopAndDestroy(); // writeStream
}
iHttpCacheObserver->StartObserver();
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::HandleCorruptEntry
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::HandleCorruptEntry(
CHttpCacheEntry& aStrayEntry,
TBool aUpdate )
{
(void)aUpdate;//suppress compiler and PC-lint warnings
#ifdef __CACHELOG__
HttpCacheUtil::WriteLog( 0, _L( "delete this stray entry" ) );
#endif
// remove from the lookuptable
iLookupTable->EraseCorruptEntry( aStrayEntry.Url() );
}
// -----------------------------------------------------------------------------
// There used to be a CHttpCacheHandler::FixLookupTableL here.
// Go back in SVN to re-discover it :)
//
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// CHttpCacheHandler::SaveBuffer
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheHandler::SaveBuffer(
CHttpCacheEntry& aEntry,
const TDesC8& aBuffer,
TBool aBody )
{
TBool ok( EFalse );
TRAPD( err, ok = CacheNeedsSpaceL( aBuffer.Length() ) );
if( err == KErrNone && ok )
{
// safe save
ok = aBody ? iStreamHandler->SaveBodyData( aEntry, aBuffer ) : iStreamHandler->SaveHeaders( aEntry, aBuffer );
}
#ifdef __CACHELOG__
else
{
// stop saving this entry
HttpCacheUtil::WriteUrlToLog( 0, _L( "item cannot be saved. remove it please" ), aEntry.Url() );
}
#endif // __CACHELOG__
return ok;
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::UpdateLookupTable
//
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::UpdateLookupTable()
{
TRAP_IGNORE(UpdateLookupTableL());
iHttpCacheObserver->StartObserver();
}
// -----------------------------------------------------------------------------
// CHttpCacheHandler::UpdateLookupTableL
// Slow method due to much file-system interaction. Don't call it from performance critical code.
// -----------------------------------------------------------------------------
//
void CHttpCacheHandler::UpdateLookupTableL()
{
CHttpCacheEvictionHandler* evictionHandler = CHttpCacheEvictionHandler::NewL();
CleanupStack::PushL(evictionHandler);
CHttpCacheLookupTable* lookupTable = CHttpCacheLookupTable::NewL( *evictionHandler, *iStreamHandler );
CleanupStack::PushL(lookupTable);
OpenLookupTableL(lookupTable);
iLookupTable->MergeL(lookupTable, iRfs);
CleanupStack::PopAndDestroy(2); // lookupTable, evictionHandler
}
// End of File