harvester/harvesterplugins/AudioPlaylistPlugin/src/harvesterm3uplaylistparser.cpp
branchRCL_3
changeset 20 f23c07ec56e2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/harvester/harvesterplugins/AudioPlaylistPlugin/src/harvesterm3uplaylistparser.cpp	Tue Aug 31 15:37:30 2010 +0300
@@ -0,0 +1,534 @@
+/*
+* Copyright (c) 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: 
+*
+*/
+
+
+#include <e32base.h>
+#include <bautils.h>
+#include <syslangutil.h>
+#include <PathInfo.h>
+#include <data_caging_path_literals.hrh>
+#include <mdsplaylisttopcharacterset.rsg>
+
+#include "harvesterm3uplaylistparser.h"
+
+#include "mdsutils.h"
+#include "harvesterlog.h"
+
+_LIT( KMDSM3ULineChange, "\n" );
+_LIT( KMDSM3UTagExtm3u, "#EXTM3U" );
+_LIT (KMDSM3UTagExtinf, "#EXTINF:" );
+_LIT( KMDSM3UPoint, ",");
+_LIT( KMDSM3UTagExt, "#");
+_LIT( KMDSM3UAbsPath, ":\\");
+
+const TInt KMDSM3UCarriageReturn = 13;
+const TInt KMDSM3UNoOffset = 0;
+const TInt KPlaylistSampleLength = 10000; 
+const TUint KUnicodeBOM = 0xFEFF;
+const TInt KPlaylistMaxSampleLength = 130000;
+const TInt KMinimumConfidenceRequired = 75;
+const TInt KMDSM3UPlaylistMaxItemCount = KMaxTInt;
+const TInt KPathStartingChars = 3;
+
+// MODULE DATA STRUCTURES
+enum TMDSM3UPlaylistLineType
+    {
+    EMDSM3UPlaylistLineTypeExtinf = 1,
+    EMDSM3UPlaylistLineTypePath = 2,
+    EMDSM3UPlaylistLineTypeNotSupported = 3,
+    EMDSM3UPlaylistLineTypeCorrupted = 4
+    };
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::NewL
+// -----------------------------------------------------------------------------
+//
+CHarvesterM3UPlaylistParser* CHarvesterM3UPlaylistParser::NewL( RFs& aFs,
+        CArrayFix<CCnvCharacterSetConverter::SCharacterSet>* aAvailableCharacterSet,
+        CArrayFix<CCnvCharacterSetConverter::SCharacterSet>* aTopCharacterSet )
+    {
+    CHarvesterM3UPlaylistParser* self = new ( ELeave ) CHarvesterM3UPlaylistParser(
+            aFs, aAvailableCharacterSet, aTopCharacterSet );
+
+	return self;
+    }
+
+
+// -----------------------------------------------------------------------------
+// Destructor
+// -----------------------------------------------------------------------------
+//
+CHarvesterM3UPlaylistParser::~CHarvesterM3UPlaylistParser()
+    {
+    Reset();
+    }
+
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::CHarvesterM3UPlaylistParser
+// -----------------------------------------------------------------------------
+//
+CHarvesterM3UPlaylistParser::CHarvesterM3UPlaylistParser( RFs& aFs,
+        CArrayFix<CCnvCharacterSetConverter::SCharacterSet>* aAvailableCharacterSet,
+        CArrayFix<CCnvCharacterSetConverter::SCharacterSet>* aTopCharacterSet ) 
+    :iFs( aFs ), iAvailableCharacterSet( aAvailableCharacterSet ),
+     iTopCharacterSet( aTopCharacterSet ), iEndLineNumber( KMDSM3UPlaylistMaxItemCount )
+    {
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ParseL
+// -----------------------------------------------------------------------------
+//
+TBool CHarvesterM3UPlaylistParser::ParseL( const TDesC& aFileName,
+		RPointerArray<HBufC>& aUriArray )
+    {
+    iPlaylistFilePath.Set( aFileName );
+    ReadPlaylistFileToBufferL();
+    ParsePlaylistBufferL( aUriArray, iInvalidItems );
+    
+    // If at the moment, we know that there is at least one error parsing
+    // with auto detect encoding, we don't need to proceed until end of
+    // file anymore, this playlist file is concluded to be corrupted
+    if ( iInvalidItems > 0 )
+        {
+        aUriArray.Reset();
+        Reset();
+        User::Leave( KErrCorrupt );
+        }
+    
+    return ETrue;
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ResetL
+// -----------------------------------------------------------------------------
+//
+void CHarvesterM3UPlaylistParser::Reset()
+    {
+    delete iBuffer;
+    iBuffer = NULL;
+    delete iLine;
+    iLine = NULL;
+    iBufferPtr.Set( KNullDesC );
+    iCurrentLineNumber = 0;
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ReadPlaylistFileToBufferL
+// -----------------------------------------------------------------------------
+//
+void CHarvesterM3UPlaylistParser::ReadPlaylistFileToBufferL()
+    {
+#ifdef _DEBUG
+    WRITELOG1( "Before reading playlist to buffer: heap size = %d", User::Heap().Size() );
+#endif
+    
+    TEntry entry;
+    User::LeaveIfError( iFs.Entry( iPlaylistFilePath, entry ) );
+    
+    HBufC* buffer = HBufC::NewLC( entry.iSize );
+    TPtr ptr = buffer->Des();
+
+    HBufC8* buf8 = HBufC8::NewLC( entry.iSize );
+    TPtr8 ptr8 = buf8->Des();
+
+    // Read the first KPlaylistSampleLength bytes of the file
+    TInt sampleLength( KPlaylistSampleLength );
+    if( sampleLength > entry.iSize )
+        {
+        sampleLength = entry.iSize;
+        }
+    User::LeaveIfError( iFs.ReadFileSection(
+                            iPlaylistFilePath, 0, ptr8, sampleLength ) );
+
+    // auto detect character encoding
+    TUint charSetId( 0 );
+    TInt error = DetectCharacterSetL( *buf8, *iTopCharacterSet, charSetId );
+    WRITELOG2("Encoding detected using top character set is 0x%x, error %d", charSetId, error);
+    
+    // when we fail to detect the encoding, use all available character set in the
+    // system to try again. If that also fails, abandon the operation.
+    if ( error )
+        {
+        User::LeaveIfError( DetectCharacterSetL( *buf8, *iAvailableCharacterSet, charSetId ) );
+        WRITELOG1( "Encoding detected using available character set is 0x%x", charSetId );     
+        }
+
+    // read the whole file if the sample taken isn't the whole file
+    if ( sampleLength != entry.iSize )
+        {
+        User::LeaveIfError( iFs.ReadFileSection(
+                                iPlaylistFilePath, 0, ptr8, entry.iSize) );
+        }
+   
+    // perform character conversion using the selected encoding
+    TInt state( CCnvCharacterSetConverter::KStateDefault );
+    TInt numOfUnconvertibleChars( 0 );
+    CCnvCharacterSetConverter* charSetConv = CCnvCharacterSetConverter::NewLC();
+    charSetConv->PrepareToConvertToOrFromL( charSetId, *iAvailableCharacterSet, iFs );
+    TInt retVal = charSetConv->ConvertToUnicode( ptr, *buf8, state, numOfUnconvertibleChars );
+    User::LeaveIfError( retVal );
+
+    // try again if the character set wasn't detected using the whole file
+    if( (retVal > 0 || numOfUnconvertibleChars > 0) && (sampleLength != entry.iSize) )
+        {
+        WRITELOG3( "retVal = %d, numOfUnconvertibleChars = %d, entry.iSize = %d",
+                retVal, numOfUnconvertibleChars, entry.iSize );  
+        numOfUnconvertibleChars = 0;
+        retVal = 0;
+        User::LeaveIfError( DetectCharacterSetL( *buf8, *iAvailableCharacterSet, charSetId ) );
+        charSetConv->PrepareToConvertToOrFromL( charSetId, *iAvailableCharacterSet, iFs );
+        retVal = charSetConv->ConvertToUnicode( ptr, *buf8, state, numOfUnconvertibleChars );
+        }
+        
+    if ( retVal > 0 || numOfUnconvertibleChars > 0 )
+        {
+        WRITELOG2( "Unable to find character encoding for the playlist file. retVal = %d, numOfUnconvertibleChars = %d",
+                    retVal, numOfUnconvertibleChars );     
+        User::Leave( KErrNotSupported );
+        }
+    
+    // remove the byte order mark (BOM) character prepended at the beginning
+    // of the stream if encoded with unicode as per Unicode section 2.4
+    if ( (charSetId == KCharacterSetIdentifierUnicodeLittle ||
+         charSetId == KCharacterSetIdentifierUnicodeBig) &&
+        ptr.Length() > 0 &&
+        ptr[0] == KUnicodeBOM )
+        {
+        ptr.Delete( 0,1 );
+        }
+        
+    iBuffer = buffer;
+    iBufferPtr.Set( *iBuffer );
+    
+    CleanupStack::PopAndDestroy (2, buf8 ); // charSetConv & buf8    
+    CleanupStack::Pop( buffer );
+        
+    // brand new buffer which hasn't been read, reset iCurrentLineNumber and
+    // iEndLineNumber
+    iCurrentLineNumber = 0;
+
+#ifdef _DEBUG
+    WRITELOG1( "After reading playlist to buffer: heap size = %d", User::Heap().Size() );
+#endif
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::DetectCharacterSetL
+// -----------------------------------------------------------------------------
+//
+TInt CHarvesterM3UPlaylistParser::DetectCharacterSetL(
+            const TDesC8& aSample,
+            const CArrayFix<CCnvCharacterSetConverter::SCharacterSet>& aCharacterSet,
+            TUint& aCharSetId)
+    {
+    // CCnvCharacterSetConverter::ConvertibleToCharSetL hangs if sample is too big
+    if ( aSample.Size() > KPlaylistMaxSampleLength )
+        {
+        User::Leave( KErrNotSupported );
+        }
+        
+    TInt confidence( 0 );
+    TInt highestConfidence( 0 );
+    TUint charSetId( 0 );
+    TUint highestConfidencecharSetId( 0 );
+
+    CCnvCharacterSetConverter* charSetConv = CCnvCharacterSetConverter::NewLC();
+    TInt count = aCharacterSet.Count();
+    for ( TInt i=0; i < count; i++ )
+        {
+        charSetId = aCharacterSet.At(i).Identifier();
+        charSetConv->ConvertibleToCharSetL( confidence, charSetId, aCharacterSet, aSample );
+        if ( confidence > highestConfidence )
+            {
+            highestConfidence = confidence;
+            highestConfidencecharSetId = charSetId;
+            }
+        }
+    CleanupStack::PopAndDestroy( charSetConv );
+    WRITELOG2( "CMPXM3uPlaylistImporter::DetectCharacterSetL :-> Confidence[%d] CharSetId[0x%x]",
+            confidence, aCharSetId );
+    if ( highestConfidence == 0 || highestConfidence < KMinimumConfidenceRequired )
+        {
+        return KErrNotFound;
+        }
+    else
+        {
+        aCharSetId = highestConfidencecharSetId;
+        return KErrNone;
+        }
+    }
+    
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ParsePlaylistBufferL
+// -----------------------------------------------------------------------------
+//
+void CHarvesterM3UPlaylistParser::ParsePlaylistBufferL(
+			RPointerArray<HBufC>& aPlaylist,
+            TInt& aInvalidItemCount)
+    {
+    // Read and process all the lines in the file
+    //
+    // the order of the following conditions is important. ReadNextLineL
+    // should be called last to avoid skipping one line
+    while ( iCurrentLineNumber < iEndLineNumber &&
+               aPlaylist.Count() < KMDSM3UPlaylistMaxItemCount &&
+               ReadNextLineL() )
+        {
+        ProcessLineL( aPlaylist, aInvalidItemCount );  
+        }
+
+    if ( aPlaylist.Count() == KMDSM3UPlaylistMaxItemCount )
+        {
+        Reset();
+        User::Leave( KErrOverflow );
+        }
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ReadNextLineL
+// -----------------------------------------------------------------------------
+//
+TBool CHarvesterM3UPlaylistParser::ReadNextLineL()
+    {
+    // iBuffer should exist when this function is called
+    __ASSERT_DEBUG( iBuffer, User::Leave( KErrBadDescriptor ) );
+
+    if ( !iBufferPtr.Length() )
+        {
+        return EFalse;
+        }
+
+    delete iLine;
+    iLine = NULL;
+
+    // Try to find line change
+    TInt offset = iBufferPtr.FindF( KMDSM3ULineChange );
+  
+    if( offset == KErrNotFound )
+        {
+        // No line change was found --> last line had no line change
+        iLine = iBufferPtr.AllocL();
+        // Set iBufferPtr to the end of buffer
+        iBufferPtr.Set( iBufferPtr.Right(0) );
+        }
+    else
+        {
+        // Found line change
+        TInt length( offset );
+        if ( (offset > KMDSM3UNoOffset) && 
+            (iBufferPtr[length - 1] == KMDSM3UCarriageReturn) )
+            {
+            --length;
+            }
+
+        iLine = iBufferPtr.Left(length).AllocL();
+
+        // Move past the line feed
+        iBufferPtr.Set( iBufferPtr.Mid(++offset) );
+        }
+
+    // Remove leading and trailing space characters from iLine's data.
+    TPtr ptr = iLine->Des();
+    ptr.Trim();
+
+    iCurrentLineNumber++;
+    return ETrue;
+    }
+    
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ProcessLineL
+// -----------------------------------------------------------------------------
+//
+void CHarvesterM3UPlaylistParser::ProcessLineL(
+			RPointerArray<HBufC>& aPlaylist,
+            TInt& aInvalidItemCount)
+    {
+    if ( iCurrentLineNumber == 1 ) // first line
+        {
+        // Check whether the file is in the extented format
+        TInt offset = iLine->Find( KMDSM3UTagExtm3u );
+        if( offset == KErrNotFound || offset != KMDSM3UNoOffset ||
+            iLine->Length() != KMDSM3UTagExtm3u().Length() )
+            {
+            // The file is not in the extented format
+            iExtendedFormat = EFalse;
+            }
+        else
+            {
+            // The file is in the extented format
+            iExtendedFormat = ETrue;
+            return;
+            }        
+        }
+    
+    // Parse line and then decide what to do with it
+    switch( ParseLineL( iItem, aInvalidItemCount ) )
+        {
+        case EMDSM3UPlaylistLineTypeExtinf:
+            // Continue to next round
+            break;
+            
+        case EMDSM3UPlaylistLineTypePath:
+            {
+            // Line was a path => add item to playlist
+            aPlaylist.AppendL( iItem.AllocL() );
+            }
+            break; 
+                       
+        case EMDSM3UPlaylistLineTypeNotSupported:
+        case EMDSM3UPlaylistLineTypeCorrupted:
+        default:
+            {
+            iItem = KNullDesC;
+            }
+            break;
+        }
+    }
+    
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ParseLineL
+// -----------------------------------------------------------------------------
+//
+TInt CHarvesterM3UPlaylistParser::ParseLineL(
+            TFileName& aItem,
+            TInt& aInvalidItemCount)
+    {
+    __ASSERT_DEBUG( iLine, User::Leave(KErrAbort) );
+
+    if( !iLine->Length() )
+        {
+        // Empty line => line is invalid
+        return EMDSM3UPlaylistLineTypeNotSupported;
+        }
+
+    if( iExtendedFormat )
+        {
+        // File is in the extented format => check whether there is extented
+        // info in this line.
+        TInt offset = iLine->Find( KMDSM3UTagExtinf );
+        if( offset != KErrNotFound && offset == KMDSM3UNoOffset )
+            {
+            offset = iLine->Find( KMDSM3UPoint );
+
+            if( offset != KErrNotFound )
+                {
+                return EMDSM3UPlaylistLineTypeExtinf; // line type extinf
+                }    
+            }
+        }
+
+    // File is not in the extented format or supported info not found from this
+    // line.
+    switch( iLine->Find(KMDSM3UTagExt) )
+        {
+        case KMDSM3UNoOffset:
+            // Unsupported extended info tag found from this line
+            return EMDSM3UPlaylistLineTypeNotSupported;
+            
+        case KErrNotFound:
+        default:
+            // Extended info not found from the beginning of line => line is
+            // a path.
+            {
+            // Get absolute path
+            TInt error( KErrNone );
+            HBufC* uri = ParseAbsolutePathLC( *iLine, error );
+        
+            if( error )
+                {
+                CleanupStack::PopAndDestroy( uri );
+                ++aInvalidItemCount;
+                return EMDSM3UPlaylistLineTypeCorrupted;
+                }
+
+            aItem = uri->Des();
+
+            CleanupStack::PopAndDestroy( uri );
+            
+            return EMDSM3UPlaylistLineTypePath; // line type path
+            }
+        }
+    }
+
+// -----------------------------------------------------------------------------
+// CHarvesterM3UPlaylistParser::ParseAbsolutePathLC
+// -----------------------------------------------------------------------------
+//
+HBufC* CHarvesterM3UPlaylistParser::ParseAbsolutePathLC(
+            const TDesC& aPath,
+            TInt& aError)
+    {
+    HBufC* path = NULL;
+    
+    TBool isAbsolute( EFalse );
+    
+    if( aPath.Length() > KPathStartingChars && 
+        !aPath.Mid(1, 2).CompareF( KMDSM3UAbsPath ) ) // magic: the 2nd and 3rd chars
+                                               // are always ":\"
+                                               // for absolute paths
+        {
+        isAbsolute = ETrue;
+        }
+
+    if( aPath.Length() > KMaxFileName ) // Test if path is too long
+        {
+        aError = KErrCorrupt;
+        }
+    else if( isAbsolute )
+        {
+        aError = KErrNone;
+        aError = iFs.IsValidName( aPath ) ? KErrNone : KErrBadName;
+        path = aPath.AllocLC();
+        }
+    else
+        {
+        // Given path could be relative => create absolute path and test it
+        // Playlist file path
+        TParse playlistPath;
+        playlistPath.Set( iPlaylistFilePath, NULL, NULL );
+        // Path to the folder, where playlist file is located to
+        TPtrC currentFolder = playlistPath.DriveAndPath();
+        // Create absolute path
+        path = HBufC::NewLC( currentFolder.Length() + aPath.Length() );
+
+        TPtr tmpPtr( path->Des() );
+        tmpPtr = currentFolder;
+        tmpPtr += aPath;
+
+        aError = iFs.IsValidName(*path) ? KErrNone : KErrBadName;
+        }
+    
+    // It is possible that a song exists in the filesystem but isn't added to
+    // the database because it's not a supported type. If such song is included
+    // in a playlist, it will be added to the database when the playlist is added.
+    // Because of this, we cannot rely on whether the song exists in the database
+    // to conclude whether the song is a broken link. We need to check for file
+    // existence here. For the unsupported songs included in the playlist, they
+    // will then be marked as corrupted when user initiates playback of those
+    // songs.
+    if ( !aError &&
+         !BaflUtils::FileExists(iFs, *path) )
+        {
+        aError = KErrPathNotFound;
+        }
+
+    return path;
+    }
+
+// End of file