diff -r b73252188534 -r f23c07ec56e2 harvester/harvesterplugins/AudioPlaylistPlugin/src/harvesterm3uplaylistparser.cpp --- /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 +#include +#include +#include +#include +#include + +#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* aAvailableCharacterSet, + CArrayFix* aTopCharacterSet ) + { + CHarvesterM3UPlaylistParser* self = new ( ELeave ) CHarvesterM3UPlaylistParser( + aFs, aAvailableCharacterSet, aTopCharacterSet ); + + return self; + } + + +// ----------------------------------------------------------------------------- +// Destructor +// ----------------------------------------------------------------------------- +// +CHarvesterM3UPlaylistParser::~CHarvesterM3UPlaylistParser() + { + Reset(); + } + + +// ----------------------------------------------------------------------------- +// CHarvesterM3UPlaylistParser::CHarvesterM3UPlaylistParser +// ----------------------------------------------------------------------------- +// +CHarvesterM3UPlaylistParser::CHarvesterM3UPlaylistParser( RFs& aFs, + CArrayFix* aAvailableCharacterSet, + CArrayFix* aTopCharacterSet ) + :iFs( aFs ), iAvailableCharacterSet( aAvailableCharacterSet ), + iTopCharacterSet( aTopCharacterSet ), iEndLineNumber( KMDSM3UPlaylistMaxItemCount ) + { + } + +// ----------------------------------------------------------------------------- +// CHarvesterM3UPlaylistParser::ParseL +// ----------------------------------------------------------------------------- +// +TBool CHarvesterM3UPlaylistParser::ParseL( const TDesC& aFileName, + RPointerArray& 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& 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& 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& 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