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

// INCLUDE FILES
#include <f32file.h>
#include "HttpCacheStreamHandler.h"
#include "HttpCacheEntry.h"
#include "HttpCacheUtil.h"
#include "HttpCacheHandler.h"
#include <centralrepository.h>
#include <SysUtilDomainCRKeys.h>

// EXTERNAL DATA STRUCTURES

// EXTERNAL FUNCTION PROTOTYPES

// CONSTANTS
const TInt KHttpCacheActiveCount = 20;
#if 0
const TInt KHttpCacheChunkSize = 2048;
#endif // 0
// MACROS

// LOCAL CONSTANTS AND MACROS

// MODULE DATA STRUCTURES

// LOCAL FUNCTION PROTOTYPES

// FORWARD DECLARATIONS

// ============================= LOCAL FUNCTIONS ===============================

// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::CHttpCacheStreamHandler
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CHttpCacheStreamHandler::CHttpCacheStreamHandler(RFs& aRFs)
    : iRfs( aRFs )
    {
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::ConstructL(
    const TDesC& aDirectory,
    TInt aCriticalLevel)
    {
    iActiveEntries = new( ELeave )CArrayPtrFlat<CHttpCacheEntry>( KHttpCacheActiveCount );
    // get drive letter for sysutil
    TParsePtrC pathParser( aDirectory );
    iDrive = pathParser.Drive();
    iCriticalLevel = aCriticalLevel;
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CHttpCacheStreamHandler* CHttpCacheStreamHandler::NewL(
    const TDesC& aDirectory ,
    TInt aCriticalLevel,
    RFs& aRFs)
    {
    CHttpCacheStreamHandler* self = new( ELeave ) CHttpCacheStreamHandler(aRFs);

    CleanupStack::PushL( self );
    self->ConstructL( aDirectory , aCriticalLevel);
    CleanupStack::Pop();

    return self;
    }

// -----------------------------------------------------------------------------
// Destructor
// -----------------------------------------------------------------------------
//
CHttpCacheStreamHandler::~CHttpCacheStreamHandler()
    {
    if ( iActiveEntries )
        {
        iActiveEntries->ResetAndDestroy();
        }
    delete iActiveEntries;
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::InitialiseCacheEntryL
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::InitialiseCacheEntryL(CHttpCacheEntry& aCacheEntry)
    {
    // create a filename for the cache entry.
    TPath sessionPath;
    User::LeaveIfError( iRfs.SessionPath( sessionPath ) );

    // Given the full URL, generates a fully qualified path for saving the HTTP response
    HBufC* bodyFileName = HttpCacheUtil::GenerateNameLC( aCacheEntry.Url(), sessionPath );
    TPtrC bodyFileNamePtr( *bodyFileName );
    aCacheEntry.SetFileNameL(bodyFileNamePtr);

    CleanupStack::PopAndDestroy(bodyFileName);

    // any more one-time initialisation to go here.
    aCacheEntry.SetState( CHttpCacheEntry::ECacheInitialized );

    // since this only happens one time, we can check if we have files left over with no index entry
    // we're too late to reuse any information stored in there, so lets just delete them.  This might prevent any
    // problems later down the line..
    iRfs.Delete(aCacheEntry.Filename());
    // header filename
    TFileName headerFileName;
    HttpCacheUtil::GetHeaderFileName( aCacheEntry.Filename(), headerFileName );
    iRfs.Delete(headerFileName);
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::Erase
//
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::Erase( CHttpCacheEntry& aCacheEntry )
    {
    HttpCacheUtil::WriteUrlToLog( 0, _L( "CHttpCacheStreamHandler::Erase - erase files associated with" ), aCacheEntry.Url() );

    // just in case it's busy being written out...
    aCacheEntry.CancelBodyWrite();

    // Delete body file
#ifndef __CACHELOG__
    iRfs.Delete( aCacheEntry.Filename() );
#else
    TInt statusBody( KErrNotFound );
    statusBody = iRfs.Delete( aCacheEntry.Filename() );
#endif
    // Adjust the size
    iContentSize -= aCacheEntry.BodySize();
    iContentSize -= aCacheEntry.HeaderSize();

#ifdef __CACHELOG__
    if ( statusBody == KErrNone )
        {
        HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheStreamHandler::Erase - SUCCESS bodyFile delete"),
                                           aCacheEntry.Filename(),
                                           aCacheEntry.Url(),
                                           aCacheEntry.BodySize(),
                                           ELogEntrySize );
        }
    else if ( statusBody == KErrNotFound )
        {
        HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheStreamHandler::Erase - CHECK bodyFile not found."),
                                           aCacheEntry.Filename(),
                                           aCacheEntry.Url(),
                                           statusBody,
                                           ELogFileErrorCode );
        }
    else
        {
    HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                       _L("CHttpCacheStreamHandler::Erase - ERROR bodyFile delete"),
                                       aCacheEntry.Filename(),
                                       aCacheEntry.Url(),
                                       statusBody,
                                       ELogFileErrorCode );
        }
#endif //__CACHELOG__
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::HeadersL
//
// -----------------------------------------------------------------------------
//
HBufC8* CHttpCacheStreamHandler::HeadersL( CHttpCacheEntry& aCacheEntry )
    {
    return aCacheEntry.HeaderData().AllocL();
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::NextChunkL
//
// -----------------------------------------------------------------------------
//
HBufC8* CHttpCacheStreamHandler::NextChunkL(
    CHttpCacheEntry& aCacheEntry,
    TBool& aLastChunk )
    {
    HBufC8 *bodyStr = NULL;

    if ( !aCacheEntry.BodyDataCached() && OpenBodyFile(aCacheEntry) )
        {
        CleanupClosePushL( aCacheEntry.BodyFile() );

        // read body
        TInt size;
        TInt err( aCacheEntry.BodyFile().Size( size ) );
        if ( err == KErrNone && size > 0 )
            {
            bodyStr = HBufC8::NewL( size );
            TPtr8 ptr( bodyStr->Des() );
            //
            err = aCacheEntry.BodyFile().Read( ptr, size );
#ifdef __CACHELOG__
            if ( err != KErrNone ) {
                HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                                   _L("CCHttpCacheStreamEntry::NextChunkL - bodyFile.read"),
                                                   aCacheEntry.Filename(),
                                                   aCacheEntry.Url(),
                                                   err,
                                                   ELogFileErrorCode );
                }
            else {
                HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                                   _L("CCHttpCacheStreamEntry::NextChunkL - bodyFile.read"),
                                                   aCacheEntry.Filename(),
                                                   aCacheEntry.Url(),
                                                   ptr.Length(),
                                                   ELogEntrySize );
                }
#endif  // __CACHELOG__
            }
        // Close body file
        CleanupStack::PopAndDestroy(1);
        }
    else
        {
        // reuse stored data if we have any.
        CSegmentedHeapBuffer& buffer = aCacheEntry.BodyData();
        TInt size = buffer.Length();
        bodyStr = HBufC8::NewL( size );
        TPtr8 ptr(bodyStr->Des());

        TInt readSegment = 0;
        TInt count = buffer.Count();
        while(readSegment < count)
            {
            TPtrC8 source = buffer.GetSegmentData(readSegment);
            ptr.Append(source);
            }
        }
    aLastChunk = ETrue;
    return bodyStr;
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::SaveHeaders
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::SaveHeaders(
    CHttpCacheEntry& aCacheEntry,
    const TDesC8& aHeaderStr )
    {
    TBool headerSaved( EFalse );
    TInt headerLen = aHeaderStr.Length();

    if ( headerLen && IsDiskSpaceAvailable( headerLen ) )
        {
        TRAPD(err, aCacheEntry.CreateHeaderBufferL(aHeaderStr));
        if ( err == KErrNone )
            {
            iContentSize += aCacheEntry.HeaderSize();
            headerSaved = ETrue;
            }
        }
    return headerSaved;
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::RemoveHeaders
//
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::RemoveHeaders( CHttpCacheEntry& aCacheEntry )
    {
    iContentSize -= aCacheEntry.HeaderSize();

    TRAP_IGNORE( aCacheEntry.CreateHeaderBufferL( 0 ) );
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::SaveBodyData
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::SaveBodyData(
    CHttpCacheEntry& aCacheEntry,
    const TDesC8& aBodyStr )
    {
    TInt bodySaved( KErrNone );
    TInt newBodyLength( aBodyStr.Length() );

    if ( newBodyLength )
        {
        TInt remainder = 0;
        CSegmentedHeapBuffer& cacheBuffer = aCacheEntry.BodyData();

        // Add data to the buffer
        TRAPD( err, cacheBuffer.AppendL(remainder, aBodyStr) );
        if ( err == KErrNone )
            {
            aCacheEntry.SetBodyDataCached(ETrue);
            }
        else
            {
            // We failed to allocate memory to store the new data in the current buffer.
            // Check to see if it's possible to write it to disk instead.
            TBool bodyFileCreated = CreateNewBodyFile( aCacheEntry );
            if ( !bodyFileCreated )
                {
                return EFalse;
                }

            TBool enoughSpace;
            enoughSpace = IsDiskSpaceAvailable( cacheBuffer.Length() + remainder );
            if ( enoughSpace )
                {
                // In this case, we have not been able to store all the data.
                // if there is enough space on disk to write everything we know
                // about now, we will flush the current buffer out synchronously.
                TInt block=0;
                TInt count = cacheBuffer.Count();
                while ( bodySaved == KErrNone && count > block )
                    {
                    TPtrC8 buf = cacheBuffer.GetSegmentData(block);
                    bodySaved = aCacheEntry.BodyFile().Write(buf);
                    }
                }
            else
                {
                // disk too full, drop the cache data
                bodySaved = KErrDiskFull;
                // reset buffers
                cacheBuffer.Reset();
                }

            if ( bodySaved == KErrNone )
                {
                // We have completed writing out the cached data, now
                // try to save the new data if we haven't run out of disk space.
                bodySaved = aCacheEntry.BodyFile().Write(aBodyStr.Right(remainder));
                }
            aCacheEntry.SetBodyDataCached(EFalse);
            aCacheEntry.BodyFile().Close();
            }
        // update size information
        aCacheEntry.SetBodySize( aCacheEntry.BodySize() + newBodyLength );
        iContentSize += aBodyStr.Length();
        }
    return ( bodySaved == KErrNone );
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::RemoveBodyData
//
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::RemoveBodyData( CHttpCacheEntry& aCacheEntry )
    {
#ifdef __CACHELOG__
     HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheStreamHandler::RemoveBodyData - CLEAR iCacheBuffer, entrySize, and iBodyFile"),
                                           aCacheEntry.Filename(),
                                           aCacheEntry.Url(),
                                           aCacheEntry.BodySize(),
                                           ELogEntrySize );
#endif

    // Remove data
    iContentSize -= aCacheEntry.BodySize();
    aCacheEntry.SetBodySize( 0 );
    aCacheEntry.BodyData().Reset();
    if ( OpenBodyFile(aCacheEntry) )
        {
        aCacheEntry.BodyFile().SetSize( 0 );
        aCacheEntry.BodyFile().Close();
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::Flush
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::Flush( CHttpCacheEntry& aCacheEntry )
    {
    TInt err( KErrNone );
    TBool bFlushed( EFalse );

    TRAP( err, bFlushed = FlushL( aCacheEntry ) );
    if ( err || !bFlushed )
        {
        return EFalse;
        }

    return ETrue;
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::FlushL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::FlushL( CHttpCacheEntry& aCacheEntry )
    {
    TInt saveOk( KErrNone );

#ifdef __CACHELOG__
    HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE:  >>StreamHander::Flush object %08x"), &aCacheEntry );
#endif
    CSegmentedHeapBuffer& cacheBuffer = aCacheEntry.BodyData();

    TInt dataLength = cacheBuffer.Length() + aCacheEntry.HeaderData().Length();

    if ( !IsDiskSpaceAvailable( dataLength ) || !CreateNewBodyFile( aCacheEntry ) )
        {
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog(0, _L("CACHEPOSTPONE:    Flush failed: not enough space or cannot create file."));
#endif
        // can't flush if not enough space, or cannot create new files
        return EFalse;
        }

    // files are open, push them onto cleanup stack.
    CleanupClosePushL( aCacheEntry.BodyFile() );

    if ( aCacheEntry.BodyDataCached() )
        {
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog(0, _L("CACHEPOSTPONE:    Body Data is Cached"));
#endif
        // append body
        TInt segment=0;
        TInt count=cacheBuffer.Count();
#ifdef __CACHELOG__
        HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE:    %d segments stored"), count);
#endif
        while ( saveOk == KErrNone && count > segment )
            {
            TPtrC8 segBuf = cacheBuffer.GetSegmentData(segment);
            saveOk = aCacheEntry.BodyFile().Write( segBuf );
#ifdef __CACHELOG__
            HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE:    segment %d write returned %d"), (segment-1), saveOk );
#endif
            }

        cacheBuffer.Reset();
        aCacheEntry.SetBodyDataCached(EFalse);
        }
    // close files
    CleanupStack::PopAndDestroy(1);

    return ( saveOk == KErrNone );
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::FlushAsync
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheStreamHandler::FlushAsync(CHttpCacheEntry& aEntry, TRequestStatus& aStatus)
    {
    TInt saveOk( KErrNone );

    TInt datalen = aEntry.BodyData().Length() + aEntry.HeaderData().Length();

#ifdef __CACHELOG__
    HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE: CHttpCacheStreamEntry::FlushAsync called on object %08x. Cached data %d bytes"), &aEntry, datalen );
#endif

    if ( datalen && aEntry.BodyData().Length() ) // don't bother writing files which have no body data
        {
        if ( IsDiskSpaceAvailable( datalen ) && CreateNewBodyFile( aEntry, ETrue ) )
            {
#ifdef __CACHELOG__
            HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE:   Triggering Async write for object 0x%08x."), &aEntry);
#endif
            // trim any spare space available.
            aEntry.BodyData().Compress();
            aEntry.WriteBodyDataAsync(aStatus);
            aEntry.SetDelayedWriteInProgress(ETrue);
            }
        else
            {
#ifdef __CACHELOG__
            HttpCacheUtil::WriteLog(0, _L("CACHEPOSTPONE:   FAILED FlushAsync."));
#endif
            // !enoughSpace
            saveOk = KErrDiskFull;
            aEntry.BodyData().Reset();
            TRequestStatus *stat = &aStatus;
            User::RequestComplete(stat, saveOk);
            }
        }
    else
        {
#ifdef __CACHELOG__
        HttpCacheUtil::WriteFormatLog(0, _L("CACHEPOSTPONE:  Not writing file %S for entry %08x since it has no data."), &(aEntry.Filename()), &aEntry );
#endif
        TRequestStatus* stat = &aStatus;
        User::RequestComplete(stat, KErrNone);
        }

    return ( saveOk == KErrNone );
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::OpenBodyFile
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::OpenBodyFile( CHttpCacheEntry& aCacheEntry )
    {
    TInt statusBody( KErrNotFound );

    // get body filename
    TFileName bodyFileName = aCacheEntry.Filename();

    statusBody = aCacheEntry.BodyFile().Open( iRfs, bodyFileName, EFileShareExclusive | EFileWrite );

    return ( statusBody == KErrNone );
    }

#if 0
// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::CreateNewFilesL
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::CreateNewFilesL( CHttpCacheEntry& aCacheEntry )
    {
    TInt statusHeader( KErrNotFound );
    TInt statusBody( KErrNotFound );

    // Create header file name from body file name
    TFileName headerFileName;
    HttpCacheUtil::GetHeaderFileName( aCacheEntry.Filename(), headerFileName );

    // Create the body file or replace it, if it exists.
    statusBody = aCacheEntry.BodyFile().Replace( iRfs, aCacheEntry.Filename(), EFileShareExclusive | EFileWrite );
    if ( statusBody == KErrNone )
        {
        // Header file should not fail
        statusHeader = aCacheEntry.HeaderFile().Replace( iRfs, headerFileName, EFileShareExclusive | EFileWrite );
        }

#ifdef __CACHELOG__
    HttpCacheUtil::WriteUrlToLog( 0, bodyFileNamePtr, aCacheEntry.Url() );
#endif

    TBool fileOk( statusHeader == KErrNone && statusBody == KErrNone );
    if ( !fileOk )
        {
        // Only the body file created, no header file, delete body file
        iRfs.Delete( aCacheEntry.Filename() );
        iRfs.Delete( headerFileName );

        aCacheEntry.SetBodySize( 0 );
        aCacheEntry.SetHeaderSize( 0 );

#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "CHttpCacheEntry::CreateNewFilesL - DELETE body file, header file failed" ) );
#endif
//        __ASSERT_DEBUG( EFalse, User::Panic( _L("CHttpCacheHandler::CreateNewFilesL Panic"), KErrCorrupt )  );
        }

    return fileOk;
    }
#endif

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::CreateNewBodyFile
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::CreateNewBodyFile( CHttpCacheEntry& aCacheEntry, TBool aUseDirectIO )
    {
    TInt statusBody( KErrNotFound );

    // Create the body file or replace it, if it exists.
#ifdef BRDO_RFILE_WRITE_DIRECT_IO_FF
    statusBody = aCacheEntry.BodyFile().Replace( iRfs, aCacheEntry.Filename(), EFileShareExclusive | EFileWrite | (aUseDirectIO ? EFileWriteDirectIO : 0) );
#else
    statusBody = aCacheEntry.BodyFile().Replace( iRfs, aCacheEntry.Filename(), EFileShareExclusive | EFileWrite );
#endif

#ifdef __CACHELOG__
    HttpCacheUtil::WriteUrlToLog( 0, aCacheEntry.Filename(), aCacheEntry.Url() );
#endif

    if ( statusBody != KErrNone )
        {
        aCacheEntry.SetBodySize( 0 );
        aCacheEntry.BodyData().Reset();
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "CHttpCacheEntry::CreateNewBodyFileL - create body file failed!" ) );
#endif
        }
    return ( statusBody == KErrNone );
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::FindCacheEntryIndex
//
// -----------------------------------------------------------------------------
//
void CHttpCacheStreamHandler::FindCacheEntryIndex(
    const CHttpCacheEntry& aCacheEntry,
    TInt* aIndex )
    {
    for ( TInt i = 0; i < iActiveEntries->Count(); i++ )
        {
        CHttpCacheEntry* entry = iActiveEntries->At( i );

        if ( entry == &aCacheEntry )
            {
            if ( aIndex )
                {
                *aIndex = i;
                }
            break;
            }
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheStreamHandler::IsDiskSpaceAvailable
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheStreamHandler::IsDiskSpaceAvailable( TInt aContentSize )
    {
    TBool diskSpaceAvailable( EFalse );
    TVolumeInfo vinfo;
    TInt errorCode = iRfs.Volume( vinfo, iDrive );

    if ( errorCode == KErrNone && ( vinfo.iFree - aContentSize ) > iCriticalLevel )
        {
        // We have space on the disk for the content
        diskSpaceAvailable = ETrue;
        }

    return diskSpaceAvailable;
    }

//  End of File
