upnp/upnpstack/upnphttptransfer/src/httpdownloadworker.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 01:12:20 +0200
changeset 0 f5a58ecadc66
permissions -rw-r--r--
Revision: 201003

/** @file
* Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "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:  Handles download of one file at a time
*
*/


// System include files
#include <sysutil.h>
#ifndef __SERIES60_31__
#include <driveinfo.h>
#endif

// User include files
#include "httpfile.h"
#include "httpdownloadworker.h"
#include "httpheader.h"
#include "httpworkerobserver.h"
#include "upnpcompvariant.hrh"

// Constants
_LIT8( KModuleName, "HttpTransfer");
const TUint KDefaultBufferSize = 64000;
const TUint32 KMaxBufferSize   = 0x100000; //1MB


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

// --------------------------------------------------------------------------
// CHttpDownloadWorker::NewL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
CHttpDownloadWorker* CHttpDownloadWorker::NewL(
                                           MHttpTransferObserver& aObserver,
                                           TUint aIAPId,
                                           TUint aBufferSize,
                                           MHttpWorkerObserver& aCallback )
    {
    CHttpDownloadWorker* self = CHttpDownloadWorker::NewLC( aObserver,
                                                            aIAPId,
                                                            aBufferSize,
                                                            aCallback );
    CleanupStack::Pop( self );
    return self;
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::NewLC()
// (See comments in header file)
// --------------------------------------------------------------------------
//
CHttpDownloadWorker* CHttpDownloadWorker::NewLC(
                                           MHttpTransferObserver& aObserver,
                                           TUint aIAPId,
                                           TUint aBufferSize,
                                           MHttpWorkerObserver& aCallback )
    {
    CHttpDownloadWorker* self = new( ELeave ) CHttpDownloadWorker(
                                                               aIAPId,
                                                               aBufferSize,
                                                               aObserver,
                                                               aCallback );
    CleanupStack::PushL( self );
    self->ConstructL();
    return self;
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::CHttpDownloadWorker()
// (See comments in header file)
// --------------------------------------------------------------------------
//
CHttpDownloadWorker::CHttpDownloadWorker( TUint /*aIAPId*/,
                                          TUint aBufferSize,
                                          MHttpTransferObserver& aObserver,
                                          MHttpWorkerObserver& aCallback )
    {
    iProcessState = EHttpTransactionIdle;
    iFileSizeFromHeader = 0;
    iBytesTransferred = 0;
    iObserver = &aObserver;
    iCallback = &aCallback;

    if ( aBufferSize <= 0 )
        {
        iBufferSize = KDefaultBufferSize;
        }
    else if ( aBufferSize > KMaxBufferSize )
        {
        iBufferSize = KMaxBufferSize;
        }
    else
        {
        iBufferSize = aBufferSize;
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::ConstructL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::ConstructL()
    {
    CHttpTransferWorker::ConstructL();
    ConnectL();
    iSessionTimer = CHttpNotifyTimer::NewL( this );
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::~CHttpDownloadWorker()
// (See comments in header file)
// --------------------------------------------------------------------------
//
CHttpDownloadWorker::~CHttpDownloadWorker()
    {
    delete iResponseBody;
    delete iSessionTimer;
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::CheckDiskSpaceL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::CheckDiskSpaceL( TInt aSize )
    {
    TInt driveNum;
    TDriveInfo aninfo;
    if ( iProcessedFile->Path() )
        {
        TParse p;
        p.Set( *(iProcessedFile->Path() ), NULL, NULL );
        TPtrC pointer = p.Drive();
        TLex lineParser( pointer.Ptr() );
        TChar ch = lineParser.Get();

        //sets the default drive or the fulnd value
        User::LeaveIfError( iFsSession.CharToDrive( ch, driveNum ) );
        }
    else
        {
        TDriveInfo driveInfo;
        User::LeaveIfError( iFile.Drive( driveNum, driveInfo ) );
        }

    User::LeaveIfError( iFsSession.Drive( aninfo, driveNum ) );

    // get the drive status from DriveInfo in order to distinguish memory card
    // and hard disk from each other in multiple drive environment.
#ifndef __SERIES60_31__
    TUint driveStatus = 0;
    User::LeaveIfError( DriveInfo::GetDriveStatus(
        iFsSession, driveNum, driveStatus ) );
#endif

    // The case for default device memory (C-drive).
    if( aninfo.iType == EMediaNANDFlash )
        {
        if( SysUtil::FFSSpaceBelowCriticalLevelL
                                        ( &iFsSession, aSize ) )
             {
             User::Leave( KErrDiskFull );
             }
        }
#ifndef __SERIES60_31__
    // Internal FLASH memory
    // Multiple drive checking rules are applied here
    // note that in case of S60 3.1 this case is skipped
    else if( aninfo.iType == EMediaHardDisk &&
             aninfo.iDriveAtt & KDriveAttRemovable &&
             driveStatus & DriveInfo::EDriveInternal )
        {
        if( SysUtil::MMCSpaceBelowCriticalLevelL( &iFsSession, aSize ) )
            {
            User::Leave( KErrDiskFull );
            }
        }
#endif
    // Memory card (or practically any other memory type)
    // This covers also emulator's EMediaRam.
    else
        {
        if( SysUtil::DiskSpaceBelowCriticalLevelL
                                        ( &iFsSession, aSize, driveNum ) )
            {
            User::Leave( KErrDiskFull );
            }
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::WriteTailIntoFileL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::WriteTailIntoFileL()
    {
    if( iResponseBody )
        {
        if( iResponseBody->Length() > 0 )
            {
            CheckDiskSpaceL( iResponseBody->Length() );
            TPtr8 ptrResponseBody = iResponseBody->Des();
#ifndef RD_UPNP_REMOTE_ACCESS
            TInt size( 0 );
            iFile.Size( size );
            if( size >= 0 )
                {
                //transfer succeed, total size can be set to file size
                //it concerns chunked encoding or HTTP1.0 
                if ( iFileSizeFromHeader == 0 )
                    {
                    iFileSizeFromHeader = size;
                    }
                
#endif //RD_UPNP_REMOTE_ACCESS
                // Write the data on the disk
                User::LeaveIfError( iFile.Write( ptrResponseBody ) );
                
                delete iResponseBody;
                iResponseBody = NULL;

                // if the tracking is on send one more progress info
#ifdef RD_UPNP_REMOTE_ACCESS
                if ( iProcessedFile->TrackingOn() )
#else // RD_UPNP_REMOTE_ACCESS
                if ( iProcessedFile->TrackingOn()  )
#endif // RD_UPNP_REMOTE_ACCESS
                    {
                    iObserver->TransferProgress( iProcessedFile->Key(),
                                                 iBytesTransferred,
                                                 iFileSizeFromHeader );
                    }
#ifndef RD_UPNP_REMOTE_ACCESS
                }
#endif //RD_UPNP_REMOTE_ACCESS
            }
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::HandleResponseDataL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::HandleResponseDataL(
                                              RHTTPTransaction aTransaction )
    {
    MHTTPDataSupplier* pRespBody = aTransaction.Response().Body();

    if( pRespBody )
        {
        TPtrC8 dataChunk;
        pRespBody->GetNextDataPart( dataChunk );

        iSessionTimer->Cancel();
        iSessionTimer->AfterSeconds( KSessionTimeout );

        // increase the progress counter
        iBytesTransferred += dataChunk.Length();

        // Allocate memory for the data
        if( !iResponseBody )
            {
            // this is the first call so create the buffer to retain
            // response data
            iResponseBody = HBufC8::NewL( iBufferSize );
            }

        if ( iResponseBody->Length() + dataChunk.Length() > iBufferSize  )
            {
            iResponseBody = iResponseBody->ReAllocL(
                                                iResponseBody->Length() +
                                                dataChunk.Length() );
            }

        // Append the new data to the buffer
        TPtr8 ptrResponseBody = iResponseBody->Des();
        ptrResponseBody.Append( dataChunk );
        pRespBody->ReleaseData();
        // If buffer is full write data to memory card ( to file )
        if( iResponseBody->Length() > iBufferSize )
            {
#ifndef RD_UPNP_REMOTE_ACCESS
            TInt size( 0 );
            // if the file handle was not valid
            User::LeaveIfError( iFile.Size( size ) );
            if( size >= 0 )
                {
#endif // RD_UPNP_REMOTE_ACCESS
                CheckDiskSpaceL( ptrResponseBody.Length() );
                User::LeaveIfError( iFile.Write( ptrResponseBody ) );
                delete iResponseBody;
                iResponseBody = NULL;
#ifndef RD_UPNP_REMOTE_ACCESS
                }                      
#endif // RD_UPNP_REMOTE_ACCESS

            // Inform observer about the progress if progress tracking is on
#ifdef RD_UPNP_REMOTE_ACCESS
            if ( iProcessedFile->TrackingOn() )
#else // RD_UPNP_REMOTE_ACCESS
            if ( iProcessedFile->TrackingOn()  )
#endif // RD_UPNP_REMOTE_ACCESS
                {
                iObserver->TransferProgress( iProcessedFile->Key(),
                                             iBytesTransferred,
                                             iFileSizeFromHeader );
                }
            }
        }

    else
        {
        aTransaction.Close();

        // Remove remains of the file from the file system
        delete iProcessedFile;
        iProcessedFile = NULL;
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::HTTPGetL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::HttpGetL()
    {
    // if URI is null -> leave
    if ( !iProcessedFile->Uri() )
        {
        User::Leave( KErrArgument );
        }

    TUriParser8 uri;
    User::LeaveIfError( uri.Parse( *( iProcessedFile->Uri() ) ) );

    RStringF method;
    method = iStringPool.StringF( HTTP::EGET, RHTTPSession::GetTable() );

    iHttpTransaction = iSession.OpenTransactionL( uri, *this, method );
    RHTTPHeaders hdr = iHttpTransaction.Request().GetHeaderCollection();

    SetHeaderL( hdr, HTTP::EUserAgent, KModuleName );

    //Set the property of upnphttptransfer to ENotifyOnDisconnect
    //Set the property of HTTP Transaction to EEnableDisconnectNotification
    //The MHFRunL can get the really http error.
    iHttpTransaction.PropertySet().SetPropertyL(
        iSession.StringPool().StringF(
            HTTP::ENotifyOnDisconnect,RHTTPSession::GetTable()),
            iSession.StringPool().StringF(
                HTTP::EEnableDisconnectNotification,
                RHTTPSession::GetTable() ));

    // Sets extra headers
    RPointerArray<CHttpHeader> headerArray = iProcessedFile->Headers();
    for ( TInt i = 0; i < headerArray.Count(); i++ )
        {
        SetHeaderL( hdr,
                    headerArray[i]->FieldName(),
                    headerArray[i]->FieldValue() );
        }

    // Now will get events in MHFRunL
    iHttpTransaction.SubmitL();

    //set timer
    iSessionTimer->Cancel();
    iSessionTimer->AfterSeconds( KSessionTimeout );

    method.Close();

    // Change state of the state machine
    iProcessState = EHttpGetSent;
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::FinishDownload()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::FinishDownload( RHTTPTransaction aTransaction )
    {
    // close transaction
    aTransaction.Close();

    delete iResponseBody;
    iResponseBody = NULL;
    
    // change state
    iProcessState = EHttpTransactionIdle;
    iProcessedFile->CloseFile(); 
    iFile.Close();

    // inform observers
    iObserver->TransferCompleted( iProcessedFile->Key(), KErrNone );

    delete iProcessedFile;
    iProcessedFile = NULL;
    
    // inform the worker observer
    iCallback->WorkerCompleted();
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::CompleteAndNotify()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::CompleteAndNotifyL( TInt aError )
    {
    iSessionTimer->Cancel();
    iHttpTransaction.Cancel();
    iHttpTransaction.Close();
    iObserver->TransferCompleted( iProcessedFile->Key(), aError );

    delete iResponseBody;
    iResponseBody = NULL;
    iProcessState = EHttpTransactionIdle;
    iCallback->WorkerCompleted();

    iFile.Close();
    iProcessedFile->DeleteFileFromFileSystemL();
    delete iProcessedFile;
    iProcessedFile = NULL;
    }


// from base class CHttpTransferWorker

// --------------------------------------------------------------------------
// CHttpDownloadWorker::CancelTransfer()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::CancelTransfer()
    {
    iSessionTimer->Cancel();
    // if transfer has not been started yet
    if ( iProcessState == EHttpWaitingForStart )
        {
        delete iProcessedFile;
        iProcessedFile = NULL;

        iProcessState = EHttpTransactionIdle;
        iCallback->WorkerCompleted();
        }
    // if process is finished do nothing
    else if ( iProcessState != EHttpTransactionIdle )
        {
        iHttpTransaction.Cancel();
        iHttpTransaction.Close();

        delete iResponseBody;
        iResponseBody = NULL;
        iProcessState = EHttpTransactionIdle;
        iCallback->WorkerCompleted();

        iFile.Close();
        TRAP_IGNORE( iProcessedFile->DeleteFileFromFileSystemL() )
        delete iProcessedFile;
        iProcessedFile = NULL;
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::StartProcessL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::StartProcessL()
    {
    CHttpTransferWorker::StartProcessL();

    iProcessedFile->CreateFileInFileSystemL();

    // File creation succeeded -> handle available
    User::LeaveIfError( iFile.Duplicate( iProcessedFile->FileHandle() ) );

    // Perform HTTP GET
    HttpGetL();
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::MHFRunL()
// (See comments in header file)
// --------------------------------------------------------------------------
//
void CHttpDownloadWorker::MHFRunL( RHTTPTransaction aTransaction,
                                   const THTTPEvent& aEvent )
    {
    //set timer
    iSessionTimer->Cancel();

    // event handler
    switch (aEvent.iStatus)
        {
        // Response headers received
        case THTTPEvent::EGotResponseHeaders:
            {
            // accept all 'Successful' range of codes 2xx
            TInt status = aTransaction.Response().StatusCode();
            if ( !( ( status >= HTTPStatus::EOk ) &&
                    ( status <  HTTPStatus::EMultipleChoices ) ) )
                {
                // calls MHFRunError
                User::Leave( aTransaction.Response().StatusCode() );
                }

            else if ( iProcessState == EHttpGetSent )
                {
                RHTTPHeaders header =
                           aTransaction.Response().GetHeaderCollection();
                RStringF field;
                field = iStringPool.StringF( HTTP::EContentLength,
                                             RHTTPSession::GetTable() );
                THTTPHdrVal value;

                // Get value of the content-length field.
                // Value is represented in bytes.
                if ( header.GetField( field, 0, value ) == KErrNone )
                    {
                    // Set the value to filesize
                    iFileSizeFromHeader = value.Int();
                    }
                else
                    {
                    // If the Content-Length is not defined
                    // in case chunked encoding or HTTP1.0 
                    // Set the iFileSizeFromHeader to zero
                    iFileSizeFromHeader = 0;
                    }

                iBytesTransferred = 0;

                iProcessState = EHttpGetResponseReceived;
                }
            iSessionTimer->AfterSeconds( KSessionTimeout );
            break;
            }

        // Response body data received
        // This is only entered when state is EHttpGetResponseReceived
        //   --> no need to check state
        case THTTPEvent::EGotResponseBodyData:
            {
            // handle body data
            // Informs the observer as well
            HandleResponseDataL( aTransaction );

            break;
            }

        // Response completed
        case THTTPEvent::EResponseComplete:
            {
            // Get response was completed
            if ( iProcessState == EHttpGetResponseReceived )
                {
                // Write last part of the buffer to file ( if exists )
                WriteTailIntoFileL();
                }

            break;
            }

        // Transaction succeeded
        case THTTPEvent::ESucceeded:
            {
            if ( iProcessState == EHttpGetResponseReceived )
                {
                // Finish download
                FinishDownload( aTransaction );
                }

            break;
            }

        // Transaction failed
        case THTTPEvent::EFailed:
            {
            CompleteAndNotifyL( KErrGeneral );
            break;
            }

        default:
            {
            if ( aEvent.iStatus < 0 )
                {
                CompleteAndNotifyL( aEvent.iStatus );
                }
            else
                {
                CompleteAndNotifyL( KErrGeneral );
                }
            break;
            }
        }
    }

// --------------------------------------------------------------------------
// CHttpDownloadWorker::MHFRunError()
// (See comments in header file)
// --------------------------------------------------------------------------
//
TInt CHttpDownloadWorker::MHFRunError( TInt aError,
                                       RHTTPTransaction /*aTransaction*/,
                                       const THTTPEvent& /*aEvent*/ )
    {
    // if the HTTP server responds with HTTP error code the aError variable
    // contains the code which is above zero, changed to KErrGeneral
    if ( aError > 0 )
        {
        aError = KErrGeneral;
        }

    TRAP_IGNORE( CompleteAndNotifyL( aError ) )

    return KErrNone;
    }

// -----------------------------------------------------------------------------
// CHttpDownloadWorker::TimerEventL
// Disconnect connection
// -----------------------------------------------------------------------------
//
void CHttpDownloadWorker::TimerEventL( CHttpNotifyTimer* /*aTimer*/ )
    {
    CompleteAndNotifyL( KErrTimedOut );
    }
// end of file