/*
* Copyright (c) 2002-2004 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:  Implements double buffering storage functionality for OMA downloads
*
*/


#include <bldvariant.hrh>

#include "FileExt.h"
#include "FileSaver.h"
#include "CodBuffStorage.h"
#include "CodLogger.h"
#include "HeaderField.h"
#include <SysUtil.h>
#include <DocumentHandler.h>
#include <APMSTD.H>
#include <f32file.h>


#ifdef RD_MULTIPLE_DRIVE
#include <driveinfo.h>
#endif //RD_MULTIPLE_DRIVE

// EXTERNAL DATA STRUCTURES
//extern  ?external_data;

// EXTERNAL FUNCTION PROTOTYPES  
//extern ?external_function( ?arg_type,?arg_type );

// CONSTANTS
//const ?type ?constant_var = ?constant;

// ---------------------------------------------------------
// CCodBuffStorage::CCodBuffStorage
// ---------------------------------------------------------
//
CCodBuffStorage::CCodBuffStorage( CFileSaver* aSaver )
:CActive( EPriorityHigh ), 
 iFile(&(aSaver->iFile)),
 iDownloadedSize(aSaver->iSize), 
 iBufferedSize(aSaver->iBufferedSize),
 iHttpStorageBufferSize(aSaver->iBufferSize),
 iHttpStorageLength(aSaver->iLength),
 iProgressiveDownload(aSaver->iProgressiveDownload)
    {
    }

// -----------------------------------------------------------------------------
// CCodBuffStorage::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CCodBuffStorage::ConstructL()
    {
   
    CLOG(( ECodStorage, 0, _L("-> CCodBuffStorage::ConstructL") ));
    CActiveScheduler::Add( this );
    }

// -----------------------------------------------------------------------------
// CCodBuffStorage::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CCodBuffStorage* CCodBuffStorage::NewL( CFileSaver* aHttpStorage )
    {
    CCodBuffStorage* self = new( ELeave ) CCodBuffStorage( aHttpStorage );
    
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop();

    return self;
    }

// ---------------------------------------------------------
// CCodBuffStorage::~CCodBuffStorage
// ---------------------------------------------------------
//
CCodBuffStorage::~CCodBuffStorage()
    {
    Cancel();

    ResetBuffers();

    delete iWritePtr; iWritePtr = 0;
    }



// ---------------------------------------------------------
// CCodBuffStorage::RunL
// ---------------------------------------------------------
//
void CCodBuffStorage::RunL()
    {
    
    CLOG(( ECodStorage, 0, _L("-> CCodBuffStorage::RunL") ));
    // Save the error code
    iLastWriteErrorCode = iStatus.Int();

    if(iLastWriteErrorCode==KErrNone && iWritePtr)
        {
        // Update how much was written on the file
        iDownloadedSize += iWritePtr->Length();
        
        CLOG(( ECodStorage, 2, _L("(%08X) CCodBuffStorage::RunL: Async write finished, downloaded now: %d"), \
                 this, iDownloadedSize ));
        }
    else
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::RunL DH-iStat: %d,"), \
               this, iStatus.Int()  ));
        }
  
    if(iWait.IsStarted())
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::RunL Stopping iWait"), \
               this ));
        iWait.AsyncStop();
        }
    }

void CCodBuffStorage::DoCancel()
    {
    CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoCancel"), \
        this ));

    // This is ok, CActive::Cancel always waits for the async operation to finish in this case
    iLastWriteErrorCode = KErrCancel;
    }

// ---------------------------------------------------------
// CCodBuffStorage::ResetBuffer
// ---------------------------------------------------------
//
void CCodBuffStorage::ResetBuffers()
    {
    CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::ResetBuffers >>"), \
        this ));


    if(IsActive())
        {
        // Make sure async writes are finished
        iWait.Start();
        }

    // Cleanup
    delete iBuff1; iBuff1 = NULL;
    delete iBuff2; iBuff2 = NULL;
    iClientBuffer = NULL;
    iBufferSize = 0;
    
    CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::ResetBuffers <<"), \
           this ));

    }
    
    
// ---------------------------------------------------------
// CCodBuffStorage::WriteOutNextBodyDataL
// ---------------------------------------------------------
//
TBool CCodBuffStorage::WriteOutNextBodyDataL( const TDesC8& aBuf )
    {
    TBool retVal( ETrue );
    
    if( iFile )
        {
        
        if(iDownloadedSize <= 0)
            {
            TInt len = aBuf.Length();
            User::LeaveIfError( iFile->Write( aBuf ) );
            User::LeaveIfError( iFile->Flush() );
    	    // Update how much written to file
   		    iDownloadedSize += len;
            }
        else
            {
            DoBufferingWriteL(aBuf);    
            }
        }        
  

    if( iHttpStorageLength != KDefaultContentLength &&
        iBufferedSize > iHttpStorageLength )
        // we don't know actually how many bytes will come down.
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::WriteOutNextBodyDataL: Length modified"), \
               this ));

        iHttpStorageLength = KDefaultContentLength;
        retVal = EFalse;
        }
        
    return retVal;
    }

// ---------------------------------------------------------
// CCodBuffStorage::FlushBuffersL
// ---------------------------------------------------------
//
void CCodBuffStorage::FlushBuffersL()
    {
    if(!iFile)
        {
        return;
        }

    CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL >>"), \
           this ));


    // Make sure async writes are finished before doing anything
    if(IsActive())
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL  stalling >>"), \
                this ));

        iWait.Start();
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL  stalling <<"), \
               this ));

        }

     if(iLastWriteErrorCode != KErrNone)
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL: last error = %d"), \
               this, iLastWriteErrorCode ));

        TInt err = iLastWriteErrorCode;
        iLastWriteErrorCode = KErrNone;
        User::Leave(err);
        }

    if(iClientBuffer)
        {
        // Flush whatever is left on the client buffer
        TInt len = iClientBuffer->Length();

        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL: %d bytes"), \
            this, len ));


        if(len > 0)
            {
            // Write to file
            TPtr8 des(iClientBuffer->Des());

            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL before iFile->Write(des)"),this ));
            User::LeaveIfError(iFile->Write(des));
            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL after iFile->Write(des)"), this ));

             // Update how much was written to file
            iDownloadedSize += len;

            des.Zero();
            }
        }

    CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::FlushBuffersL  <<"), \
        this ));

   }

// ---------------------------------------------------------
// CCodBuffStorage::DoBufferingWriteL
// ---------------------------------------------------------
//        
void CCodBuffStorage::DoBufferingWriteL(const TDesC8& aBuf)
    {
    if((iBufferSize != iHttpStorageBufferSize )|| (iClientBuffer == NULL))
        {
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: Allocate %d - downloaded %d"), \
        this, iHttpStorageBufferSize, iBufferedSize ));

        
        // Make sure nothing is leaked
        delete iBuff1; iBuff1 = 0;
        delete iBuff2; iBuff2 = 0;
        iBufferSize = 0;
        
        // Allocate buffers
        iBuff1 = HBufC8::NewL(iHttpStorageBufferSize);
        iBuff2 = HBufC8::NewL(iHttpStorageBufferSize);

        // Mark that we now have buffers that are iBufferSize big
        iBufferSize = iHttpStorageBufferSize;

        // Set client to use buffer 1
        iClientBuffer = iBuff1;
        
        }

    // Get incoming data pointer and size
    const TUint8* src = aBuf.Ptr();
    TInt incomingBuffSize = aBuf.Length();
    
    // Process the data in blocks  
    while(incomingBuffSize)
        {
        // Get pointer to current client buffer data (at start of loop since iClientBuffer can change)
        TPtr8 buff = iClientBuffer->Des();
        TInt buffLength = buff.Length();

        // How much space in the buffer
        TInt toFillUp = iBufferSize - buffLength;

        // Check if we can just fit everything in the current buffer
        if(incomingBuffSize < toFillUp)
            {  
            // Dump data on the current client buffer since ample space exists
            buff.Append(src, incomingBuffSize);

            // Update the 'received' counter on how much we got from air
            iBufferedSize += incomingBuffSize;

            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: Copied %d, now %d / %d"), \
                   this, incomingBuffSize, buff.Length(), iBufferSize ));


            // And break out of the loop since we're finished for this round
            break;
            }
            
        // At this point we have data that either fills or overflows the buffer so append as much as fits
        buff.Append( src, toFillUp );

        // Update the 'received' counter on how much we copied
        iBufferedSize+=toFillUp;

        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: Copied %d, now %d / %d"), \
               this, toFillUp, buff.Length(), iBufferSize ));


        // Subtract how much we copied from the loop counter and advance source data pointer
        incomingBuffSize -= toFillUp;
        src+=toFillUp;

        // Now we have a full client buffer, better do something with it
    
        // Check if previous async write is still ongoing
        // Done here so if somebody switched on progressive download midway through we don't mix buffers
        if(IsActive())
            {
            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: stalling >>"), \
                   this ));

            iWait.Start();
            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: stalling <<"), \
                   this ));

             }

        // In case of async writes we have to check if there was error previously
        if(iLastWriteErrorCode != KErrNone)
            {
            // Clear the error code and leave with it
            TInt err = iLastWriteErrorCode;
            iLastWriteErrorCode = KErrNone;
            User::Leave(err);
            }

        if(iProgressiveDownload)
            {
            CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL: Buffer full -> Progressive download"), \
                   this ));

            TInt len = buff.Length();

            // Progressive download -> just do a synchronous write and update counter
            User::LeaveIfError(iFile->Write(buff));
            
            // Update received counter
            //iBufferedSize += toFillUp;

            // Update how much was written to file (sync write)
            iDownloadedSize += len;

            // Clear out client buffer
            buff.Zero();
            
            // Continue for next write
            continue;
            }

        // We must keep the descriptor alive until async operations have finished
        if(!iWritePtr)
            {
            iWritePtr = new (ELeave) TPtr8(iClientBuffer->Des());
            }
        else
            {
            iWritePtr->Set(iClientBuffer->Des());
            }

#ifdef _DEBUG
        // Print out file position so we can check alignments
        TInt currentPos(0);
        iFile->Seek(ESeekCurrent, currentPos);
        
        CLOG(( ECodStorage, 2, _L("(%08X)  CCodBuffStorage::DoBufferingWriteL - Start async write %d bytes, filepos=%d"), \
               this, iWritePtr->Length(), currentPos ));

#endif

        // Start the async write and set AO active
        iFile->Write( *iWritePtr, iStatus ) ;
        SetActive();

        // Swap buffer pointers (we can't use the same buffer here 
        // since it is not known when the buffer can be used again)
        if(iClientBuffer == iBuff1)
            {
            iClientBuffer = iBuff2;
            }
        else
            {
            iClientBuffer = iBuff1;
            }

        // Make sure the new client buffer is empty
        iClientBuffer->Des().Zero();
        }
    }
