mtpfws/mtpfw/datatypes/src/cmtptypefile.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 01:11:40 +0200
changeset 0 d0791faffa3f
child 1 f8e15b44d440
permissions -rw-r--r--
Revision: 201003 Kit: 201005

// Copyright (c) 2006-2009 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:
//

/**
 @file
 @publishedPartner
 */

#include <mtp/cmtptypefile.h>
#include <mtp/mtpdatatypeconstants.h>

// File type constants.
const TInt KMTPFileChunkSizeForLargeFile(0x00080000); // 512K

//For file size less than 512K, we will use this smaller chunk size to reduce the heap usage.
const TInt KMTPFileChunkSizeForSmallFile(0x00010000); //64K

//For file size larger than it, we will split one setSize() to several smaller one, each with the following size.
const TInt64 KMTPFileSetSizeChunk(1<<30); //1G

const TUint KUSBHeaderLen = 12;

/**
 MTP file object data type factory method. 
 @param aFs The handle of an active file server session.
 @param aName The name of the file. Any path components (i.e. drive letter
 or directory), which are not specified, are taken from the session path. 
 @param aMode The mode in which the file is opened (@see TFileMode).
 @return A pointer to the MTP file object data type. Ownership IS transfered.
 @leave One of the system wide error codes, if a processing failure occurs.
 @see TFileMode
 */
EXPORT_C CMTPTypeFile* CMTPTypeFile::NewL(RFs& aFs, const TDesC& aName, TFileMode aMode)
    {
    CMTPTypeFile* self = NewLC(aFs, aName, aMode);
    CleanupStack::Pop(self);
    return self;
    }

/**
 MTP file object data type factory method. A pointer to the MTP file object data
 type is placed on the cleanup stack.
 @param aFs The handle of an active file server session.
 @param aName The name of the file. Any path components (i.e. drive letter
 or directory), which are not specified, are taken from the session path. 
 @param aMode The mode in which the file is opened (@see TFileMode).
 @return A pointer to the MTP file object data type. Ownership IS transfered.
 @leave One of the system wide error codes, if a processing failure occurs.
 @see TFileMode
 */
EXPORT_C CMTPTypeFile* CMTPTypeFile::NewLC(RFs& aFs, const TDesC& aName, TFileMode aMode)
    {
    CMTPTypeFile* self = new(ELeave) CMTPTypeFile;
    CleanupStack::PushL(self);
    self->ConstructL(aFs, aName, aMode);
    return self;
    }

EXPORT_C CMTPTypeFile* CMTPTypeFile::NewL(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
    {
    CMTPTypeFile* self = NewLC(aFs, aName, aMode,aRequiredSize,aOffSet);
	CleanupStack::Pop(self);
    return self;
    }

EXPORT_C CMTPTypeFile* CMTPTypeFile::NewLC(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
    {
    CMTPTypeFile* self = new(ELeave) CMTPTypeFile;
    CleanupStack::PushL(self);
    self->ConstructL(aFs, aName, aMode, aRequiredSize, aOffSet);
    return self;
    }

/**
 Destructor
 */
EXPORT_C CMTPTypeFile::~CMTPTypeFile()
    {
    if(iCurrentCommitChunk.Length() != 0)
        {
        ToggleRdWrBuffer();
        }

    iFile.Close();

    iBuffer1.Close();
    iBuffer2.Close();
    Cancel();
    }

/**
 Sets the size of the file, this function must be called in case of file writting/receiving. related resouce
 will be allocated in this function to prepare to receive the incoming data.
 @param aSize The new size of the file (in bytes).
 @leave One of the system wide error codes, if a processing failure occurs.
 */
EXPORT_C void CMTPTypeFile::SetSizeL(TUint64 aSize)
    {
    iTargetFileSize = (TInt64)aSize; //keep a record for the target file size
    
    iRemainingDataSize = (TInt64)aSize;//Current implemenation does not support file size with 2 x64 

    if(iRemainingDataSize> KMTPFileChunkSizeForLargeFile) //512K
        {
        iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
        iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
        }
    else
        {
        iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
        iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
        }
    if(iRemainingDataSize> KMTPFileSetSizeChunk)
        {
        //split the setSize to multiple calling of 512M
        User::LeaveIfError(iFile.SetSize(KMTPFileSetSizeChunk));
        iCurrentFileSetSize = KMTPFileSetSizeChunk;
        }
    else
        {
        User::LeaveIfError(iFile.SetSize(aSize));
        iCurrentFileSetSize = aSize;
        }
    }

/**
 Provides a reference to the native file object encapsulate by the MTP file 
 object data type.
 @return The native file object reference.
 */
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
EXPORT_C RFile64& CMTPTypeFile::File()
#else
EXPORT_C RFile& CMTPTypeFile::File()
#endif
    {
    return iFile;
    }

EXPORT_C TInt CMTPTypeFile::FirstReadChunk(TPtrC8& aChunk) const
    {
    aChunk.Set(NULL, 0);
    iReadSequenceState = EIdle;
    iBuffer1.Zero();
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
    TInt64 pos =iOffSet;
#else
    TInt pos = static_cast<TInt>(iOffSet);
#endif
    TInt err(iFile.Seek(ESeekStart, pos));
    if (err == KErrNone)
        {
        // The USB SIC header is 12 bytes long. If the first chunk is 128K - 12 bytes, 
        // the USB SIC transport will not buffer data, which will improve the transfer rate.
        err = iFile.Read(iBuffer1, iBuffer1.MaxLength() - KUSBHeaderLen);
        if (err == KErrNone)
            {
            //this chunk is going to be used by USB to read  data from it, only CMTPTypefile::RunL() can toggle this flag
            //When it finishe reading data into Buffer2.
            iBuffer1AvailForWrite = EFalse;

            aChunk.Set(iBuffer1.Ptr(), iBuffer1.Length());

            //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
            iCurrentCommitChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());

            iRemainingDataSize -= aChunk.Length();

            if (aChunk.Length() == 0)
                {
                // Empty File.
                iReadSequenceState = EIdle;
                err = KMTPChunkSequenceCompletion;
                }
            else
                {
                if (iRemainingDataSize <= 0)
                    {
                    // EOF.
                    iReadSequenceState = EIdle;
                    aChunk.Set(aChunk.Ptr(), aChunk.Length() + iRemainingDataSize);  //for partial
                    err = KMTPChunkSequenceCompletion;
                    }
                else
                    {
                    iReadSequenceState = EInProgress;
                    //This is NOT the last chunk, issue more CMTPTypeFile::RunL()
                    if (!IsActive())
                        {
                        //Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
                        const_cast<CMTPTypeFile*>(this)->SetActive();
                        TRequestStatus* status = (TRequestStatus*)&iStatus;
                        User::RequestComplete(status, KErrNone);
                        }
                    else
                        {
                        //This is a very extreme cases, it only occurs when the following assumption is met
                        //1. USB already took buffer1 and already issue CMTPTypeFileRunL(), therefore, the ActiveObject has completed itself, 
                        //2. Somehow, this active object is not scheduled to be running even after the USB already use out the other buffer.
                        //3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
                        //4. USB call this function again to get the other data buffer.
                        //5. Then it find the previous active is not scheduled to run.
                        //in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
                        //the 2nd USB active. but for multi-core platform, this should be re-evaluated .
                        iReadSequenceState = EIdle;
                        err = KMTPChunkSequenceCompletion;
                        }
                    }
                }
            }
        else
            {
            iReadSequenceState = EIdle;
            iFileRdWrError = ETrue;
            }
        }
    iByteSent += aChunk.Length();
    return err;
    }

EXPORT_C TInt CMTPTypeFile::NextReadChunk(TPtrC8& aChunk) const
    {
    TInt err(KErrNone);

    if((iReadSequenceState != EInProgress) || (iFileRdWrError))
        {
        aChunk.Set(NULL, 0);
        return KErrNotReady;
        }

    //This is called by USB's RunL(), here, the only possible scenarios is that the CMTPTypleFile::RunL() issued in FirReadChunk or last NextReadChunk must 
    //have already finished. Now take the buffer which is filled in by data in CMTPTypleFile::RunL().
    aChunk.Set(iCurrentCommitChunk.Ptr(), iCurrentCommitChunk.Length());
    if(iBuffer1AvailForWrite)
        {//We have already used buffer_1, now buffer2 contains data read into by CMTPTypeFile::RunL();
        //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
        iCurrentCommitChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
        }
    else
        {
        //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
        iCurrentCommitChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());
        }

    iRemainingDataSize -= aChunk.Length();

    if(aChunk.Length() == 0)
        {
        iReadSequenceState = EIdle;
        err = KMTPChunkSequenceCompletion;
        }
    else if(iRemainingDataSize> 0)
        {
        //This is NOT the last chunk, issue more CMTPTypeFile::RunL()
        if (!IsActive())
            {
            //Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
            ((CMTPTypeFile*)this)->SetActive();
            TRequestStatus* status = (TRequestStatus*)&iStatus;
            User::RequestComplete(status, KErrNone);
            }
        else
            {
            //This is a very extreme cases, it only occurs when the following assumption is met
            //1. USB already took buffer1 and already issue CMTPTypeFileRunL(), therefore, the ActiveObject has completed itself, 
            //2. Somehow, this active object is not scheduled to be running even after the USB already use out the other buffer.
            //3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
            //4. USB call this function again to get the other data buffer.
            //5. Then it find the previous active is not scheduled to run.
            //in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
            //the 2nd USB active. but for multi-core platform, this should be re-evaluated .
            iReadSequenceState = EIdle;
            err = KMTPChunkSequenceCompletion;
            }
        }
    else
        {//Last Chunk. Do not issue Active object. and indicate this completion of the chunk
        iReadSequenceState = EIdle;
        aChunk.Set(aChunk.Ptr(), aChunk.Length() + iRemainingDataSize); //for partial
        err = KMTPChunkSequenceCompletion;
        }
    iByteSent += aChunk.Length();
    return err;
    }

EXPORT_C TInt CMTPTypeFile::FirstWriteChunk(TPtr8& aChunk)
    {
    __ASSERT_DEBUG(iBuffer1AvailForWrite, User::Invariant());
    __ASSERT_DEBUG(!iFileRdWrError, User::Invariant());

    aChunk.Set(NULL, 0, 0);
    iWriteSequenceState = EIdle;
    
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
    TInt64 pos =0;
#else
    TInt pos =0;
#endif
    TInt err(iFile.Seek(ESeekStart, pos));
    if (err == KErrNone)
        {
        //Because USB HS's transmission rate is several time faster than the rate of writting data into File System.
        //If the first packet is a full chunk size packet, then the writting of that data will not start until the full-chunk
        //sized packet is received. Here we intentionly reduce the first packet size to 1/4 of the full chunk size, therefore,
        //the start of writting data into File system will start only after 1/4 of the full chunk size data is received.
        //This can make the writting of data to FS start earlier.
        aChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
        iWriteSequenceState = EInProgress;

        //this chunk is going to be used by Transport to write data into it, and when it is full, transport
        //will call back CommitChunkL(), at that time, the EFalse means it already contains data in it.
        //it is ready for reading data from it. 
        //This is a initial value for it to trigger the double-buffering mechanism.
        iBuffer1AvailForWrite = EFalse;
        }

    return err;
    }

EXPORT_C TInt CMTPTypeFile::NextWriteChunk(TPtr8& aChunk)
    {
    TInt err(KErrNone);
    aChunk.Set(NULL, 0, 0);

    if (iWriteSequenceState != EInProgress)
        {
        err = KErrNotReady;
        }
    else
        {//toggle between buffer 1 and buffer 2 here.
        if(iBuffer1AvailForWrite)
            {
            aChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
            }
        else
            {
            aChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());
            }
        }

    return err;
    }

EXPORT_C TUint64 CMTPTypeFile::Size() const
    {
    //The USB transport layer uses USB Container Length to determine the total size of data to be
    //transfered. In USB protocol, the Container Length is 32 bits long which is up to 4G-1, so 
    //for synchronization of a large file >=4G-12 bytes (the USB header is 12 bytes long), the
    //Container Length can't be used to determine the total size of data any more. In this kind of
    //case, our USB transport layer implementation will call this function to get the actual data size.
    
    //The RFile::SetSize() method may take over 40 seconds if we create a file and set its size
    //to a very large value, and this will cause timeout in MTP protocol layer. To avoid this 
    //timeout, when creating a large file(over 512MB), instead of setting its size directly to
    //the target size by one singile RFile::SetSize() call, we'll call RFile::SetSize() multiple
    //times and set the file size step by step acumulately. For example, for a 2GB file, its
    //size will be set to 0.5G first, and then 1G, 1.5G and at last 2G.
    
    //So if a file is transfering to device, the size of the file that returned by RFile::Size() is
    //just a temporary value and means nothing. In this case, let's return the target file size instead.
    if(!iFileOpenForRead && iRemainingDataSize)
        {        
        return iTargetFileSize;
        }
    
    //If the initiator get partial of the file, return the requested partial size 
    if (iFileOpenForRead && iTargetFileSize)
        {
        return iTargetFileSize;
        }
    
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
    TInt64 size;
#else
    TInt size;
#endif
    iFile.Size(size);
    return size;
    }

EXPORT_C TUint CMTPTypeFile::Type() const
    {
    return EMTPTypeFile;
    }

EXPORT_C TBool CMTPTypeFile::CommitRequired() const
    {
    return ETrue;
    }

EXPORT_C MMTPType* CMTPTypeFile::CommitChunkL(TPtr8& aChunk)
    {
	if(iFileRdWrError)
		{
		return NULL;
		}
	if(0 == aChunk.Length())
		{
		ToggleRdWrBuffer();
		return NULL;
		}
    iCurrentCommitChunk.Set(aChunk);

    if(iRemainingDataSize> iCurrentCommitChunk.Length())
        {//This is NOT the last chunk, we issue an active object to commit it to File system.
        iRemainingDataSize -= iCurrentCommitChunk.Length();
		/*
		if (!IsActive())
			{
			//Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
			SetActive();
			TRequestStatus* thisAO = &iStatus;
			User::RequestComplete(thisAO, KErrNone);
			}
		else
			{
			//This is a very extreme cases, it only occurs when the following assumption is met
			//1. USB received buffer1 and already call this CommitChunkL(), therefore, the ActiveObject has completed itself, and USB then use another buffer to
			//receive the data.
			//2. Somehow, this active object is not scheduled to be running even after the USB already fill out the other buffer.
			//3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
			//4. USB call this function again to commit the other data buffer.
			//5. Then it find the previous active is not scheduled to run.
			//in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
			//the 2nd USB active. but for multi-core platform, this should be re-evaluated .
			iFileRdWrError = ETrue;//if it really discard the incoming recevied file.			
			//__FLOG(_L8("\nThe program should not arrive here !!!!!\n"));
			}
			*/
        }
    else
        {//This is the last chunk, we synchronous commit it 
        iRemainingDataSize = 0;
        ToggleRdWrBuffer();
			return NULL;
        }
	return this;
    }

//for partial
EXPORT_C Int64 CMTPTypeFile::GetByteSent()
    {
    return iByteSent;
    }

CMTPTypeFile::CMTPTypeFile() :
    CActive(EPriorityUserInput), iBuffer1AvailForWrite(ETrue),
        iFileRdWrError(EFalse), iCurrentCommitChunk(NULL, 0)
    {
    CActiveScheduler::Add(this);
    }

void CMTPTypeFile::ConstructL(RFs& aFs, const TDesC& aName, TFileMode aMode)
    {
    if (aMode & EFileWrite)
        {
        iFileOpenForRead = EFalse;
        User::LeaveIfError(iFile.Replace(aFs, aName, aMode|EFileWriteDirectIO));
        }
    else
        {
        iFileOpenForRead = ETrue;
        User::LeaveIfError(iFile.Open(aFs, aName, aMode|EFileReadDirectIO|EFileShareReadersOnly));
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
        TInt64 size = 0;
#else
        TInt size = 0;
#endif
        User::LeaveIfError(iFile.Size(size));
        iRemainingDataSize = size;

        //For File reading, NO "SetSizeL()" will be called, therefore, create the buffer here.
        if (iRemainingDataSize > KMTPFileChunkSizeForLargeFile) //512K
            {
            iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
            iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
            }
        else
            {
            iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
            iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
            }
        }
    }

void CMTPTypeFile::ConstructL(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
	{
    if (aMode & EFileWrite)
        {
        iFileOpenForRead = EFalse;
        User::LeaveIfError(iFile.Replace(aFs, aName, aMode|EFileWriteDirectIO));
        }
    else
        {
        iFileOpenForRead = ETrue;
        iOffSet = aOffSet;
        User::LeaveIfError(iFile.Open(aFs, aName, aMode|EFileReadDirectIO|EFileShareReadersOnly));
#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
        TInt64 size = 0;
#else
        TInt size = 0;
#endif
        User::LeaveIfError(iFile.Size(size));
        
        if(aRequiredSize < size)
            {
            iTargetFileSize = aRequiredSize;
            }
        else
            {
            iTargetFileSize = size;
            }
        iRemainingDataSize = iTargetFileSize;
        
        //For File reading, NO "SetSizeL()" will be called, therefore, create the buffer here.
        if (iRemainingDataSize > KMTPFileChunkSizeForLargeFile) //512K
            {
            iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
            iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
            }
        else
            {
            iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
            iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
            }
        }
	}

void CMTPTypeFile::DoCancel()
    {
    // Nothing to cancel here because this Active object does not issue any asynchronous call to others.
    }

// Catch any leaves - the CActiveScheduler can't handle it.
TInt CMTPTypeFile::RunError(TInt /* aError*/)
    {
    //We did not throw exception in RunL() in reality, therefore, we need not to cope with it.
    return KErrNone;
    }

void CMTPTypeFile::RunL()
    {
    ToggleRdWrBuffer();
    }

void CMTPTypeFile::ToggleRdWrBuffer()
    {
    //This is triggered by CommitChunkL(), this will write the received data into File system synchronously.
    //Since someone trigger this RunL(), therefore, there must be one of 2 buffer which is full of data to wait for writing buffer data into File system.
    //Each RunL(), only need to commit one chunk because transport only prepare one chunk for file system in one RunL().

    TInt err = KErrNone;

    if (!iFileOpenForRead)
        {
        if (!iFileRdWrError)
            {
            TInt64 temp = iCurrentCommitChunk.Length();            
            iTotalReceivedSize += temp;
            if (iTotalReceivedSize > iCurrentFileSetSize)
                {
                //temp += iRemainingDataSize;//Total uncommitted file size.
                temp = iTotalReceivedSize-iCurrentFileSetSize+iRemainingDataSize;
                if (temp >= KMTPFileSetSizeChunk)
                    {
                    iCurrentFileSetSize += KMTPFileSetSizeChunk;
                    }
                else
                    {
                    iCurrentFileSetSize += temp;
                    }
                err = iFile.SetSize(iCurrentFileSetSize);
                }
                        
            if (err != KErrNone)
                {
                iFileRdWrError = ETrue;
                }
            else
                {
                err = iFile.Write(iCurrentCommitChunk);
                if (err != KErrNone)
                    {// file Write failed,	this means we cannot successfully received this file but however, we cannot disrupt a current DIOR phase according to MTP spec.
                    // We should continue to receive the data and discard this data, only after the data  phase is finished can we send back an error response
                    //to Initiator. Therefore, we pretend to continue to write this data into file, and let final processor to check the file size and then give back a 
                    //corresponding error code to MTP initiator.
                    iFileRdWrError = ETrue;
                    iFile.SetSize(0);
                    }
                }
            }
        iCurrentCommitChunk.Zero();
        }
    else
        {
        if (!iFileRdWrError)
            {
            err = iFile.Read(iCurrentCommitChunk,
                    iCurrentCommitChunk.MaxLength());
            if (err != KErrNone)
                {//Error, abort the current file reading.
                iFileRdWrError = ETrue;
                }
            }
        }

    iBuffer1AvailForWrite = !iBuffer1AvailForWrite;//toggle the flag.
    }