webengine/osswebengine/cache/src/HttpCacheHandler.cpp
author Kiiskinen Klaus (Nokia-D-MSW/Tampere) <klaus.kiiskinen@nokia.com>
Mon, 30 Mar 2009 12:54:55 +0300
changeset 0 dd21522fd290
child 1 7c90e6132015
permissions -rw-r--r--
Revision: 200911 Kit: 200912

/*
* 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