harvester/harvesterplugins/AudioPlaylistPlugin/src/harvesterm3uplaylistparser.cpp
/*
* 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