First implementation of asynchronous feed parser asynchparser
authorSebastian Brannstrom <sebastianb@symbian.org>
Fri, 09 Jul 2010 11:34:00 +0100
branchasynchparser
changeset 171 cc1be3797632
parent 170 f42c9de433f2
child 250 5f35fce156f8
First implementation of asynchronous feed parser
application/group/Podcast.mmp
application/src/PodcastFeedView.cpp
engine/inc/FeedEngine.h
engine/inc/FeedParser.h
engine/src/FeedEngine.cpp
engine/src/FeedParser.cpp
--- a/application/group/Podcast.mmp	Wed Jul 07 23:27:49 2010 +0100
+++ b/application/group/Podcast.mmp	Fri Jul 09 11:34:00 2010 +0100
@@ -106,5 +106,4 @@
 LIBRARY ws32.lib 
 LIBRARY hlplch.lib
 LIBRARY bitgdi.lib 
-LIBRARY  apmime.lib    // TDataType
 LIBRARY  commonui.lib  // CDocumentHandler
--- a/application/src/PodcastFeedView.cpp	Wed Jul 07 23:27:49 2010 +0100
+++ b/application/src/PodcastFeedView.cpp	Fri Jul 09 11:34:00 2010 +0100
@@ -230,6 +230,7 @@
 
 void CPodcastFeedView::FeedDownloadStartedL(TFeedState /*aState*/, TUint aFeedUid)
 	{
+	DP("FeedDownloadStartedL");
 	// Update status text
 	iUpdatingRunning = ETrue;
 	UpdateFeedInfoStatusL(aFeedUid, ETrue);
@@ -239,6 +240,7 @@
 
 void CPodcastFeedView::FeedDownloadFinishedL(TFeedState aState,TUint aFeedUid, TInt aError)
 	{
+	DP("FeedDownloadFinishedL");
 	switch(aError)
 		{
 		case KErrCouldNotConnect:
@@ -255,6 +257,7 @@
 			break;
 		}
 	UpdateFeedInfoStatusL(aFeedUid, EFalse);
+	UpdateToolbar();
 	}
 
 void CPodcastFeedView::UpdateFeedInfoStatusL(TUint aFeedUid, TBool aIsUpdating)
--- a/engine/inc/FeedEngine.h	Wed Jul 07 23:27:49 2010 +0100
+++ b/engine/inc/FeedEngine.h	Fri Jul 09 11:34:00 2010 +0100
@@ -47,11 +47,13 @@
 const TInt KMaxLineLength = 4096;
 const TInt KMaxSearchString = 30;
 
-enum TClientState {
+enum TFeedEngineState {
 	EIdle,
-	EUpdatingFeed,
-	EUpdatingImage,
-	ESearching
+	EDownloadingFeed,
+	EParsingFeed,
+	EDownloadingImage,
+	ESearching,
+	EParsingOpml
 };
 
 class CFeedEngine : public CBase, public MHttpClientObserver, public MFeedParserObserver
@@ -81,7 +83,7 @@
 	/**
 	 * Returns the current internal state of the feed engine4
 	 */
-	IMPORT_C TClientState ClientState();
+	IMPORT_C TFeedEngineState ClientState();
 
 	/**
 	 * Returns the current updating client UID if clientstate is != ENotUpdateing
@@ -118,7 +120,6 @@
 	
 	void UpdateNextFeedL();
 	void NotifyOpmlParsingCompleteL(TInt aError, TUint aNumFeedsAdded);
-
 	
 private:
 	void DBLoadFeedsL();
@@ -132,7 +133,7 @@
 		
 private:
 	CHttpClient* iFeedClient;
-	TClientState iClientState;
+	TFeedEngineState iEngineState;
 	CFeedTimer iFeedTimer;
 
 	CPodcastModel& iPodcastModel;	
@@ -152,8 +153,8 @@
     RArray<MFeedEngineObserver*> iObservers;
     
     // new feeds only add one show to download list when auto downloading
-    TBool newFeed;
-    TUint showsAdded;
+    TBool iNewFeed;
+    TUint iShowsAdded;
     
     sqlite3& iDB;
     
--- a/engine/inc/FeedParser.h	Wed Jul 07 23:27:49 2010 +0100
+++ b/engine/inc/FeedParser.h	Fri Jul 09 11:34:00 2010 +0100
@@ -25,6 +25,7 @@
 #include <xml/contenthandler.h>	// for Xml::MContentHandler
 #include <xml/documentparameters.h>
 
+
 _LIT(KTagItem, "item");
 _LIT(KTagTitle, "title");
 _LIT(KTagImage, "image");
@@ -62,15 +63,21 @@
 
 const int KBufferLength = 1024;
 
-class CFeedParser : public CBase, public Xml::MContentHandler 
+class CFeedParser : public CActive, public Xml::MContentHandler 
 {
 public:
+	static CFeedParser* NewL(MFeedParserObserver& aCallbacks, RFs& aFs);
+	static CFeedParser* NewLC(MFeedParserObserver& aCallbacks, RFs& aFs);
+	virtual ~CFeedParser();
+	
+private:
 	CFeedParser(MFeedParserObserver& aCallbacks, RFs& aFs);
-	virtual ~CFeedParser();
+	void ConstructL();
 	
 public:
 	void ParseFeedL(const TFileName &feedFileName, CFeedInfo *item, TUint aMaxItems);
-	
+    void ParseFeedAoL(const TFileName &feedFileName, CFeedInfo *item, TUint aMaxItems);
+
 public: // from MContentHandler
 	void OnStartDocumentL(const Xml::RDocumentParameters& aDocParam, TInt aErrorCode);
 	void OnEndDocumentL(TInt aErrorCode);
@@ -85,6 +92,12 @@
 	void OnError(TInt aErrorCode);
 	TAny* GetExtendedInterface(const TInt32 aUid);
 	CFeedInfo& ActiveFeed();
+	
+private:
+   void DoCancel();
+   void RunL();
+   TInt RunError(TInt aError);
+   
 private:
 	MFeedParserObserver& iCallbacks;
 	TFeedState iFeedState;
@@ -98,7 +111,13 @@
 	TUint iItemsParsed;
 	TBool iStoppedParsing;
 	TEncoding iEncoding;
+	
 	RFs& iRfs;
+    Xml::CParser* iParser;
+    HBufC8* iXmlBuffer;
+    RFile iFile;
+    TFileName iFileName;
+
 };
 
 #endif
--- a/engine/src/FeedEngine.cpp	Wed Jul 07 23:27:49 2010 +0100
+++ b/engine/src/FeedEngine.cpp	Fri Jul 09 11:34:00 2010 +0100
@@ -41,7 +41,7 @@
 
 void CFeedEngine::ConstructL()
 	{
-	iParser = new (ELeave) CFeedParser(*this, iPodcastModel.FsSession());
+	iParser = CFeedParser::NewLC(*this, iPodcastModel.FsSession());
 	
 	iFeedClient = CHttpClient::NewL(iPodcastModel, *this);
 	iFeedTimer.ConstructL();
@@ -80,11 +80,12 @@
     	TRAP_IGNORE(ImportFeedsL(importFile));
 		}
     
+	CleanupStack::Pop(iParser);
 	RunFeedTimer();
 	}
 
 CFeedEngine::CFeedEngine(CPodcastModel& aPodcastModel)
-		: iClientState(EIdle),
+		: iEngineState(EIdle),
 		  iFeedTimer(this),
 		  iPodcastModel(aPodcastModel),
 		  iDB(*aPodcastModel.DB())
@@ -107,9 +108,9 @@
 /**
  * Returns the current internal state of the feed engine4
  */
-EXPORT_C TClientState CFeedEngine::ClientState()
+EXPORT_C TFeedEngineState CFeedEngine::ClientState()
 	{
-	return iClientState;
+	return iEngineState;
 	}
 
 
@@ -144,7 +145,7 @@
 
 EXPORT_C void CFeedEngine::UpdateAllFeedsL(TBool aAutoUpdate)
 	{
-	if (iClientState != EIdle)
+	if (iEngineState != EIdle)
 		{
 		User::Leave(KErrInUse);
 		}
@@ -169,7 +170,7 @@
 
 EXPORT_C void CFeedEngine::CancelUpdateAllFeeds()
 	{
-	if(iClientState != EIdle)
+	if(iEngineState != EIdle)
 		{
 		iCancelRequested = ETrue;
 		iFeedsUpdating.Reset();
@@ -181,7 +182,7 @@
 	{
 	DP1("UpdateNextFeed. %d feeds left to update", iFeedsUpdating.Count());
 	
-	if (iClientState != EIdle)
+	if (iEngineState != EIdle)
 		{
 		User::Leave(KErrInUse);
 		}
@@ -216,7 +217,7 @@
 		}
 	else
 		{
-		iClientState = EIdle;
+		iEngineState = EIdle;
 		for (TInt i=0;i<iObservers.Count();i++) 
 			{
 			TRAP_IGNORE(iObservers[i]->FeedUpdateAllCompleteL(iAutoUpdatedInitiator?MFeedEngineObserver::EFeedAutoUpdate:MFeedEngineObserver::EFeedManualUpdate));
@@ -239,10 +240,10 @@
 
 	if (iActiveFeed->LastUpdated() == 0)
 		{
-		newFeed = ETrue;	
+		iNewFeed = ETrue;	
 		}
 	
-	showsAdded = 0;
+	iShowsAdded = 0;
 	
 	iActiveFeed->SetLastError(KErrNone);
 	DBUpdateFeedL(*iActiveFeed);
@@ -254,7 +255,7 @@
 	_LIT(KFileNameFormat, "%lu.xml");
 	iUpdatingFeedFileName.AppendFormat(KFileNameFormat, aFeedUid);
 	
-	iClientState = EUpdatingFeed;
+	iEngineState = EDownloadingFeed;
 			
 	for (TInt i=0;i<iObservers.Count();i++)
 		{
@@ -284,24 +285,24 @@
 	aItem.SetDescriptionL(*description);
 	CleanupStack::PopAndDestroy(description);
 
-	if (newFeed) {
+	if (iNewFeed) {
 		// for new feeds, set all shows played
 		aItem.SetPlayState(EPlayed);
 		// except the first one
-		if (showsAdded == 0) {
+		if (iShowsAdded == 0) {
 			aItem.SetPlayState(ENeverPlayed);	
 		}
 	}
 	
 	TRAPD(err, iPodcastModel.ShowEngine().AddShowL(aItem));
-
+	
 	if (err == KErrNone && aItem.PlayState() == ENeverPlayed && 
 			iPodcastModel.SettingsEngine().DownloadAutomatically()) 
 		{
 		iPodcastModel.ShowEngine().AddDownloadL(aItem);
+		iShowsAdded++;
 		}
 	
-	showsAdded++;
 	}
 
 void CFeedEngine::GetFeedImageL(CFeedInfo *aFeedInfo)
@@ -327,7 +328,7 @@
 
 	if(iFeedClient->GetL(aFeedInfo->ImageUrl(), filePath, ETrue))
 		{
-			iClientState = EUpdatingImage;
+			iEngineState = EDownloadingImage;
 		}
 	}
 
@@ -527,6 +528,7 @@
 	TBuf<KMaxLineLength> title;
 	title.Copy(item->Title());
 	item->SetTitleL(title); // if this leaves we are out of memory
+	CompleteL(NULL, KErrNone);
 	}
 
 
@@ -555,26 +557,32 @@
 
 void CFeedEngine::CompleteL(CHttpClient* /*aClient*/, TInt aError)
 	{
-	DP2("CFeedEngine::CompleteL BEGIN, iClientState=%d, aSuccessful=%d", iClientState, aError);
+	DP2("CFeedEngine::CompleteL BEGIN, iEngineState=%d, aSuccessful=%d", iEngineState, aError);
 
-	switch(iClientState)
+	switch(iEngineState)
 		{		
-		case EUpdatingFeed: 
+		case EDownloadingFeed: 
 		{
-		iClientState = EIdle;
 		switch (aError)
 			{
 			case KErrCancel:						
 				{
 				iFeedsUpdating.Reset();
+				iEngineState = EIdle;
 				}
 				break;
 			case KErrCouldNotConnect:
 				iFeedsUpdating.Reset();
+				iEngineState = EIdle;
 				break;
 			default:
 				{
-				if (!iCancelRequested) {
+				if (iCancelRequested)
+					{
+					iEngineState = EIdle;
+					}
+				else
+					{
 					iActiveFeed->SetLastError(aError);
 					TTime time;
 					time.HomeTime();
@@ -585,60 +593,64 @@
 						// Parse the feed. We need to trap this call since it could leave and then
 						// the whole update chain will be broken
 						// change client state
-						TRAPD(parserErr, iParser->ParseFeedL(iUpdatingFeedFileName, iActiveFeed, iPodcastModel.SettingsEngine().MaxListItems()));
+						TRAPD(parserErr, iParser->ParseFeedAoL(iUpdatingFeedFileName, iActiveFeed, iPodcastModel.SettingsEngine().MaxListItems()));
 	
 						if(parserErr)
 							{
 							// we do not need to any special action on this error.
 							iActiveFeed->SetLastError(parserErr);
 							DP1("CFeedEngine::Complete()\t Failed to parse feed. Leave with error code=%d", parserErr);
+							iEngineState = EIdle;
 							}
-
-						// delete the downloaded XML file as it is no longer needed
-						BaflUtils::DeleteFile(iPodcastModel.FsSession(),iUpdatingFeedFileName);			
-	
-						// if the feed has specified a image url. download it if we dont already have it
-						if((iActiveFeed->ImageUrl().Length() > 0))
+						else 
 							{
-							if ( (iActiveFeed->ImageFileName().Length() == 0) || 
-									(iActiveFeed->ImageFileName().Length() > 0 && 
-											!BaflUtils::FileExists(iPodcastModel.FsSession(), 
-													iActiveFeed->ImageFileName()) )
-							)
-								{
-								TRAPD(error, GetFeedImageL(iActiveFeed));
-								if (error)
-									{
-									// we have failed in a very early stage to fetch the image.
-									// continue with next Feed update	
-									iActiveFeed->SetLastError(parserErr);
-									iClientState = EIdle;							
-									}
-								}	
+							iEngineState = EParsingFeed;
 							}
 						}
 					else
 						{
 						// even if it fails, this will allow us to move on
-						iClientState = EIdle;
+						iEngineState = EIdle;
 						}
 					}
-				iCancelRequested = EFalse;
 				}break;
 			}
-			DBUpdateFeedL(*iActiveFeed);
 			NotifyFeedUpdateComplete(iActiveFeed->Uid(), aError);
-
-			// we will wait until the image has been downloaded to start the next feed update.
-			if (iClientState == EIdle)
+			}
+		break;
+		case EParsingFeed: 
+			{
+			// if the feed has specified a image url. download it if we dont already have it
+			if((iActiveFeed->ImageUrl().Length() > 0))
 				{
-				UpdateNextFeedL();	
+				if ( (iActiveFeed->ImageFileName().Length() == 0) || 
+						(iActiveFeed->ImageFileName().Length() > 0 && 
+								!BaflUtils::FileExists(iPodcastModel.FsSession(), 
+										iActiveFeed->ImageFileName()) )
+				)
+					{
+					TRAPD(error, GetFeedImageL(iActiveFeed));
+					if (error)
+						{
+						// we have failed in a very early stage to fetch the image.
+						// continue with next Feed update	
+						iActiveFeed->SetLastError(error);
+						iEngineState = EIdle;
+						NotifyFeedUpdateComplete(iActiveFeed->Uid(), error);
+						}
+					}
+				else
+					{
+					iEngineState = EIdle;
+					NotifyFeedUpdateComplete(iActiveFeed->Uid(), aError);
+					}
 				}
-			}break;
-		case EUpdatingImage:
+			}
+			break;
+		case EDownloadingImage:
 			{
 			// change client state to not updating
-			iClientState = EIdle;
+			iEngineState = EIdle;
 			if(aError == KErrNone)
 				{
 				// now the image has been downloaded, so we set it again in the FeedInfo to start
@@ -647,13 +659,11 @@
 				TRAP_IGNORE(iActiveFeed->SetImageFileNameL(*fileNameCopy, &iPodcastModel));
 				CleanupStack::PopAndDestroy(fileNameCopy);
 				}
-			DBUpdateFeedL(*iActiveFeed);
 			NotifyFeedUpdateComplete(iActiveFeed->Uid(), aError);
-			UpdateNextFeedL();
 			}break;
 		case ESearching: 
 			{
-			iClientState = EIdle;
+			iEngineState = EIdle;
 	
 			DP2("Search complete, results in %S with error %d", &iSearchResultsFileName, aError);
 			if(aError == KErrNone)
@@ -685,7 +695,16 @@
 
 void CFeedEngine::NotifyFeedUpdateComplete(TInt aFeedUid, TInt aError)
 	{
-	DP("CFeedEngine::NotifyFeedUpdateComplete");	
+	DP("CFeedEngine::NotifyFeedUpdateComplete");
+	
+	DBUpdateFeedL(*iActiveFeed);
+	
+	// we will wait until the image has been downloaded to start the next feed update.
+	if (iEngineState == EIdle)
+		{
+		UpdateNextFeedL();	
+		}
+	
 	for (TInt i=0;i<iObservers.Count();i++) 
 		{
 		TRAP_IGNORE(iObservers[i]->FeedDownloadFinishedL(MFeedEngineObserver::EFeedAutoUpdate, aFeedUid, aError));
@@ -1084,7 +1103,7 @@
 	{
 	DP1("FeedEngine::SearchForFeedL BEGIN, aSearchString=%S", &aSearchString);
 	
-	if (iClientState != EIdle) {
+	if (iEngineState != EIdle) {
 		User::Leave(KErrInUse);
 	}
 	TBuf<KMaxURLLength> ssBuf;
@@ -1107,11 +1126,11 @@
 	// run search
 	if(iFeedClient->GetL(*url, iSearchResultsFileName, iPodcastModel.SettingsEngine().SpecificIAP()))
 		{
-		iClientState = ESearching;
+		iEngineState = ESearching;
 		}
 	else
 		{
-		iClientState = EIdle;
+		iEngineState = EIdle;
 		User::Leave(KErrAbort);
 		}
 	
--- a/engine/src/FeedParser.cpp	Wed Jul 07 23:27:49 2010 +0100
+++ b/engine/src/FeedParser.cpp	Fri Jul 09 11:34:00 2010 +0100
@@ -28,24 +28,73 @@
 #include "podcastutils.h"
 
 using namespace Xml;
+
 const TInt KMaxParseBuffer = 1024;
 const TInt KMaxStringBuffer = 100;
+const TInt KFileBufferSize = 1024; // buffer size for file reading
+_LIT8( KXmlMimeType, "text/xml" );
 
-CFeedParser::CFeedParser(MFeedParserObserver& aCallbacks, RFs& aFs) : 	iCallbacks(aCallbacks), iRfs(aFs)
+CFeedParser* CFeedParser::NewL(MFeedParserObserver& aCallbacks, RFs& aFs)
+	{
+	CFeedParser* self = CFeedParser::NewLC(aCallbacks, aFs);
+	CleanupStack::Pop();
+	return self;
+	}
+
+CFeedParser* CFeedParser::NewLC(MFeedParserObserver& aCallbacks, RFs& aFs)
+	{
+	CFeedParser* self = new ( ELeave ) CFeedParser(aCallbacks, aFs);
+	CleanupStack::PushL( self);
+	self->ConstructL();
+	return self;
+	}
+
+CFeedParser::CFeedParser(MFeedParserObserver& aCallbacks, RFs& aFs) : CActive(EPriorityLow), 
+		iCallbacks(aCallbacks), iRfs(aFs)
 {
+	CActiveScheduler::Add( this);
 }
 
 CFeedParser::~CFeedParser()
 {	
 }
 
+void CFeedParser::ParseFeedAoL(const TFileName &feedFileName, CFeedInfo *item, TUint aMaxItems)
+	{
+	DP("CFeedParser::ParseFeedAoL BEGIN");
+	// Remember to cancel any outstanding request first.
+	if ( IsActive())
+	{
+	   Cancel();
+	}
+	
+	iActiveFeed = item;
+	iFeedState = EStateRoot;
+	iActiveShow = NULL;
+	iItemsParsed = 0;
+	iMaxItems = aMaxItems;
+	iStoppedParsing = EFalse;
+	iEncoding = ELatin1;
+	iFileName.Copy(feedFileName);
+
+	User::LeaveIfError( iFile.Open( iRfs, feedFileName, EFileRead));
+
+	delete iXmlBuffer;
+	iXmlBuffer = 0;
+	iXmlBuffer = HBufC8::NewL( KFileBufferSize);
+	TPtr8 bufferPtr( iXmlBuffer->Des());
+	iFile.Read( bufferPtr, KFileBufferSize, iStatus);
+	SetActive();
+
+	iParser->ParseBeginL();
+	DP("CFeedParser::ParseFeedAoL END");
+	}
+
+
 void CFeedParser::ParseFeedL(const TFileName &feedFileName, CFeedInfo *info, TUint aMaxItems)
 	{
-	//DP1("ParseFeedL BEGIN: %S", &feedFileName);		
-	
-	_LIT8(KXmlMimeType, "text/xml");
-	// Contruct the parser object
-	CParser* parser = CParser::NewLC(KXmlMimeType, *this);
+	DP1("ParseFeedL BEGIN: %S", &feedFileName);		
+
 	iActiveFeed = info;
 	iFeedState = EStateRoot;
 	iActiveShow = NULL;
@@ -53,12 +102,14 @@
 	iMaxItems = aMaxItems;
 	iStoppedParsing = EFalse;
 	iEncoding = ELatin1;
+	iFileName.Copy(feedFileName);
+	CParser* parser = CParser::NewLC(KXmlMimeType, *this);
 
 	ParseL(*parser, iRfs, feedFileName);
 
 	CleanupStack::PopAndDestroy(parser);	
 	
-	//DP("ParseFeedL END");
+	DP("ParseFeedL END");
 	}
 
 // from MContentHandler
@@ -81,7 +132,7 @@
 
 void CFeedParser::OnEndDocumentL(TInt /*aErrorCode*/)
 	{
-	//DP("OnEndDocumentL()");
+	DP("OnEndDocumentL()");
 	iCallbacks.ParsingCompleteL(iActiveFeed);
 	}
 
@@ -94,7 +145,7 @@
 	
 	TBuf<KMaxStringBuffer> str;
 	str.Copy(aElement.LocalName().DesC());
-	//DP2("OnStartElementL START state=%d, element=%S", iFeedState, &str);
+	DP2("OnStartElementL START state=%d, element=%S", iFeedState, &str);
 	iBuffer.Zero();
 	switch (iFeedState) {
 	case EStateRoot:
@@ -219,7 +270,7 @@
 	TBuf<KMaxStringBuffer> str;
 	str.Copy(aElement.LocalName().DesC());
 
-	//DP2("OnEndElementL START state=%d, element=%S", iFeedState, &str);
+	DP2("OnEndElementL START state=%d, element=%S", iFeedState, &str);
 
 	switch (iFeedState) {
 		case EStateChannelTitle:
@@ -449,3 +500,52 @@
 	{
 		return *iActiveFeed;
 	}
+
+void CFeedParser::ConstructL()
+	{
+	iParser = CParser::NewL( KXmlMimeType, *this);	
+	}
+
+void CFeedParser::DoCancel()
+	{
+	
+	}
+
+void CFeedParser::RunL()
+	{
+	if ( KErrNone == iStatus.Int())
+	{
+	   if ( iXmlBuffer->Length()== 0) // end of the file.
+	   { 
+	    iParser->ParseEndL();
+	    iFile.Close();
+		BaflUtils::DeleteFile(iRfs,iFileName);			
+
+	    delete iXmlBuffer;
+	    iXmlBuffer = 0;
+	   }
+	   else
+	   {// Otherwise, we continue reading the next chunk of the XML file.
+	    // Parse the next "part" of the XML document.
+	    iParser->ParseL( *iXmlBuffer);
+
+	    // Read the next chunk of the file.
+	    TPtr8 bufferPtr( iXmlBuffer->Des());
+	    iFile.Read( bufferPtr, KFileBufferSize, iStatus);
+
+	    // Don't forget to call this... :)
+	    SetActive();
+	   }
+	}
+	else
+	{
+	   // Do something if error happens.
+	}
+	}
+
+TInt CFeedParser::RunError(TInt aError)
+	{
+	
+	}
+
+