phonebookengines_old/contactsmodel/tsrc/T_RemoteView.cpp
branchRCL_3
changeset 19 5b6f26637ad3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/phonebookengines_old/contactsmodel/tsrc/T_RemoteView.cpp	Tue Aug 31 15:05:21 2010 +0300
@@ -0,0 +1,720 @@
+// Copyright (c) 2003-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:
+// T_RemoteView creates a database in the main thread. Another thread is created containing
+// a remote view to the same database.  A number of contacts are added, then some deleted. 
+// Meanwhile whenever the view is updated and available, the second thread prints all the
+// contact IDs to the console using an active object, one at a time (a nicely time-consuming
+// process intended to cause a backlog of messages between the thread). 
+// After the contacts are added, a number of them are deleted at once.
+// Before the changes for INC037352 , this would Panic. Now it executes without any errors. 
+// Note: This overwrites the default contacts database:  c:\system\data\Contacts.cdb
+// This is necessary if this is to be used with phonebook server which requires this file
+// 
+//
+
+// System includes
+#include <cntdb.h>
+#include <cntdef.h>
+#include <cntitem.h>
+#include <cntfield.h>
+#include <cntfldst.h>
+
+// System includes
+#include <e32std.h>
+#include <e32twin.h>
+#include <e32test.h>
+#include <cntdbobs.h>
+
+// User includes
+#include "t_utils2.h"
+
+// Constants
+
+_LIT(KThreadNameOne, "OneThread");
+
+_LIT(KViewNameOne, "T_RemoteView-ViewOne");
+
+const TInt KNumberOfContacts = 300;
+const TInt KNumberOfContactsForTest2 = 300;
+const TInt KNumberToDeleteAtOneTime = 45;
+
+static RTest TheTest(_L("T_RemoteView"));
+
+//
+//#pragma mark ==== Class definitions ====
+//
+
+/** 
+This class recieves view events, prints them, and then prints all the
+contacts in the view. If a view changes while printing, it will stop, 
+starting again from the top when the view becomes available again
+*/
+class CViewObsever : public CActive, public MContactViewObserver
+	{
+	public: 
+		CViewObsever(RTest& aTest);
+		void HandleContactViewEvent(const CContactViewBase& aView,const TContactViewEvent& aEvent);
+
+	protected: 
+		void RunL();
+		void DoCancel();
+		TInt RunError(TInt aError);
+
+	private:
+		void StartPrintingAllContacts(const CContactViewBase& aView);
+		TBool iReady;
+		RTest& iTest;
+		TInt iCount;
+		TInt iIndex;
+		const CContactViewBase* iView;
+	};
+
+/** This class is used to create a named remote view  in its own thread, displaying the contents
+	 in a console */
+class CViewer : public CBase
+	{
+	public: 
+		static TThreadId Create(const TDesC& aName, const TDesC& aViewName);
+		static TInt LaunchThread(TAny* aAny);
+		static CViewer* NewL(const TDesC& aViewName);
+		static TInt CheckFinished(TAny* self);
+		~CViewer();
+
+	private: 
+		void ConstructL(const TDesC& aViewName);
+		TBool IsDone();
+
+	private:
+		RTest* iTest;
+		CContactDatabase* iDatabase;
+		RContactViewSortOrder iViewSortOrder;
+		CViewObsever* iEventObserver;
+		CContactNamedRemoteView* iView;
+		RSemaphore iSemaphore;
+		CIdle* iSemaphoreWatcher;
+	};
+
+
+//#pragma mark -
+
+CViewObsever::CViewObsever(RTest& aTest) : CActive(EPriorityStandard+4), iTest(aTest)
+	{
+	}
+
+/** Tell to print all contacts in the view (using RunL) */
+void CViewObsever::StartPrintingAllContacts(const CContactViewBase& aView) 
+	{
+	ASSERT(iReady);
+	
+	if(IsActive())
+		{
+		Cancel();		
+		}
+	
+	iView = &aView;
+	TRAPD(err, iCount = aView.CountL() );
+	if(err!=KErrNone)
+		{
+		RunError(err);
+		return;
+		}
+	iIndex=iCount;
+	TRequestStatus* stat = &iStatus;
+	User::RequestComplete(stat,KErrNone);
+	SetActive();
+	}
+
+
+/** Print any error. If it's simply not ready, ignore it (but end the printing cycle) */
+/** And, if it's not found ... do the same ... this happens when the test system is slow
+either due to other processes running or the speed of the processor itself.  If the test
+in question is Test2L, the attempt to gain a count may have indicated the view cannot be
+found. */
+TInt CViewObsever::RunError(TInt aError)
+	{
+	iTest.Printf(_L("Error: %i\n"),aError);
+	if(aError == KErrNotReady | aError == KErrNotFound)		
+	// view is not ready or count returned not found
+		{	
+		/* Can only get here by a Leave in AtL() (or by being called manually an equvalent Leaver in CountL() in StartPrintingAllContacts() ) 
+			This means the object thinks the state is ready, but it really is not .
+			This can *only* ever happen if an EUnavailable event got sent,
+			but it's not been recieved yet. 
+			So, to save the trouble set the readiness to false and just sit and wait
+			for the EUnavailable and (eventually) the EReady events
+		*/
+		iReady=EFalse;
+		return KErrNone;
+		}
+	else if(aError == KErrNotFound)
+		{
+		return KErrNone;
+		}
+	return aError;
+	}
+
+/** Print the next item in the view*/
+void CViewObsever::RunL()
+	{
+	if(iReady && --iIndex > (iCount-10))
+		{
+		iView->AtL(iIndex);
+		
+		TRequestStatus* stat = &iStatus;
+		User::RequestComplete(stat,KErrNone);
+		SetActive();
+		}
+	iTest.Printf(_L("\n"));
+	}
+
+/** Stop printing */
+void CViewObsever::DoCancel()
+	{
+	iIndex = iCount;
+	iTest.Printf(_L("Cancelling view observer\n"));
+	}
+
+/** Print the current readiness state and the incoming event.  If the view is ready, start printing the list of contacts*/
+void CViewObsever::HandleContactViewEvent(const CContactViewBase& aView,const TContactViewEvent& aEvent)
+	{
+	TPtrC name(RThread().Name());
+	if(iReady) 
+		{
+		iTest.Printf(_L("%S Ready: "), &name);
+		}
+	else 
+		{
+		iTest.Printf(_L("%S Not ready: "), &name);
+		}
+	switch(aEvent.iEventType)
+		{
+		case TContactViewEvent::EUnavailable: 
+			iReady = EFalse;
+			iTest.Printf(_L("EUnavailable\n"));
+			break;
+		case TContactViewEvent::EReady:
+			iReady = ETrue;
+			iTest.Printf(_L("EReady\n"));
+			break;
+		case TContactViewEvent::ESortOrderChanged:
+			iTest.Printf(_L("ESortOrderChanged\n"));
+			break;
+		case TContactViewEvent::ESortError:
+		case TContactViewEvent::EServerError:
+		case TContactViewEvent::EIndexingError:
+			iTest.Printf(_L("Error: %x %i\n"),aEvent.iEventType, aEvent.iInt);
+			iReady = EFalse;
+			break;
+		case TContactViewEvent::EItemAdded:
+			iTest.Printf(_L("EItemAdded %i (%i)\n"), aEvent.iContactId, aEvent.iInt);
+			break;
+		case TContactViewEvent::EItemRemoved:
+			iTest.Printf(_L("EItemRemoved\n"));
+			break;
+		case TContactViewEvent::EGroupChanged:
+			iTest.Printf(_L("EGroupChanged\n"));
+			break;
+		default:
+			return;	
+		}
+	
+	if(iReady)
+		{
+		StartPrintingAllContacts(aView);
+		}
+	}
+	
+//#pragma mark -
+
+CViewer* CViewer::NewL(const TDesC& aViewName)
+	{
+	CViewer* self = new(ELeave) CViewer;
+	CleanupStack::PushL(self);
+	self->ConstructL(aViewName);
+	CleanupStack::Pop(self);
+	return(self);
+	}
+
+CViewer::~CViewer()
+	{
+	TPtrC name(RThread().Name());
+	iTest->Printf(_L("Deleting viewer: %S\n"), &name);
+	iView->Close(*iEventObserver);
+	delete iEventObserver;
+	iViewSortOrder.Close();
+	
+	delete iDatabase;
+	iSemaphore.Close();
+	iTest->End();
+	iTest->Close();
+	delete iTest;
+	delete iSemaphoreWatcher;
+	CActiveScheduler::Stop();
+	}
+
+void CViewer::ConstructL( const TDesC& aViewName)
+	{
+
+	TPtrC name(RThread().Name());
+
+	// Create and name an RTest
+	iTest = new(ELeave) RTest(name);
+	CConsoleBase* console = iTest->Console();
+	// Position our console window
+	if(console) 
+		{
+		TSize size = console->ScreenSize();
+		size.iWidth = size.iWidth /2 -3;
+		console = Console::NewL(name, size);
+		}
+	else
+		{
+		TSize size(40,10);
+		console = Console::NewL(name, size);
+		}
+	delete const_cast<RTest*>(iTest)->Console();
+	iTest->SetConsole(console);
+	console->SetTitle(name);
+	iTest->Start(_L("Thread: Starting Viewer"));
+	
+	iTest->Next(_L("Thread: Creating view sort order"));
+	iViewSortOrder.AppendL(KUidContactFieldFamilyName);
+   	iViewSortOrder.AppendL(KUidContactFieldGivenName);
+    iViewSortOrder.AppendL(KUidContactFieldCompanyName);
+
+	iTest->Next(_L("Thread: Creating view observer"));
+	iEventObserver = new(ELeave) CViewObsever(*iTest);
+	
+	User::LeaveIfError(iSemaphore.OpenGlobal(aViewName));
+
+	User::After(2000000);		// give the other thread a chance to do stuff
+	TInt err=KErrNotFound;
+	TInt count=0;
+	while(err!=KErrNone)
+		{
+		if(count) 
+			{
+			iTest->Printf(_L("  retry %i\n"),count);
+			}
+		else
+			{
+			iTest->Next(_L("Thread: Starting Database"));
+			}
+		TRAP(err, iDatabase = CContactDatabase::OpenL());
+		count++;
+		if(count>30)
+			{
+			User::Leave(err);
+			}
+		}
+
+	CActiveScheduler::Add(iEventObserver);
+	
+	iTest->Next(_L("Thread: Starting View"));
+	iView = CContactNamedRemoteView::NewL(*iEventObserver, aViewName, *iDatabase, iViewSortOrder, EContactsOnly);
+
+	iSemaphore.Signal(); // main thread can start doing things
+	iSemaphoreWatcher = CIdle::NewL(CActive::EPriorityIdle-100);			// really very idle
+	TCallBack callback(CViewer::CheckFinished,this); 
+	iSemaphoreWatcher->Start(callback);
+	}
+
+TBool CViewer::IsDone()
+	{
+		return ETrue;
+	}
+
+TInt CViewer::CheckFinished(TAny* self)
+	{
+	if( ((CViewer*) self)->IsDone() )
+		{
+		delete  ((CViewer*) self);
+		return false;
+		}
+	return true;
+	}
+
+static CViewer* InitL(TDesC& aViewName)
+	{
+	CActiveScheduler *scheduler=new(ELeave) CActiveScheduler;
+	CActiveScheduler::Install(scheduler);
+	return CViewer::NewL(aViewName);
+	}
+
+TInt CViewer::LaunchThread(TAny* aAny) 
+	{
+	__UHEAP_MARK;
+	CTrapCleanup* cleanUpStack=CTrapCleanup::New();
+	if (!cleanUpStack)
+		{
+		return KErrNoMemory;
+		}
+	TRAPD(r,InitL(*((TDesC*) aAny)));
+	if (r==KErrNone)
+		{
+		User::After(2000000);		// give the other thread a chance to do stuff
+		CActiveScheduler::Start();
+		User::After(2000000);		// give the other thread a chance to do stuff
+		}
+	delete CActiveScheduler::Current();
+	delete cleanUpStack;
+	
+	__UHEAP_MARKEND;
+	return r;
+	}
+
+/* This is the only thing one needs to call in order to set the view in motion. */
+/** Creates a remote view in its own thread. 
+@param aName			the name of the thread
+@param aViewName	the name of the view*/
+TThreadId CViewer::Create(const TDesC& aName, const TDesC& aViewName)
+	{
+	RThread thread;
+	TInt err = thread.Create( aName, CViewer::LaunchThread, KDefaultStackSize, 0x2000, 0x20000, (void*) &aViewName, EOwnerThread );
+	if(err!=KErrNone)
+		{
+		return KNullUidValue;
+		}
+	thread.Resume();
+	thread.SetPriority(EPriorityNormal);
+	TThreadId id = thread.Id();
+	thread.Close();
+	return id;
+	}
+
+
+
+//
+//#pragma mark -
+//
+
+/** This object creates and closes a thread containing a view to the contacts database. 
+	The view will output all changes and the current state of the database using an RDebug object. 
+	This thread acts completely independently until it is told to close. 
+	@see CViewObsever
+
+	Call Open() to create initailise the other thread. WaitUntilDbIsReady() or Close() must be called eventually. 
+	if CheckIfDbIsReady() returns true, WaitUntilDbIsReady() will return immediately. 
+	Close() is synchronous and will not return until the other thread dies. 
+*/
+class RRemoteViewThread
+	{
+	public:
+		TInt Open(const TDesC& aViewName, const TDesC& aThreadName);
+		void WaitUntilDbIsReady(); 
+		TBool CheckIfDbIsReady();
+		TInt Close();
+
+	private:
+		RSemaphore iSemaphore;
+		TBool iDbReady;
+		TRequestStatus iStatus;
+	};
+
+/** Create and initalise the view thread. 
+	There is no reason why the view name and thread name cannot be the same.
+	This call is more or less asynchronous, and the thread will not be immediately ready.
+	Call CheckIfDbIsReady() or WaitUntilDbIsReady() to find out if the thread is ready.
+	This can be closed at any time, even if the thread is not ready.
+@param	aViewName	The name of the view
+@param aThreadName	The name of the thread. 
+@return the error state
+*/
+
+TInt RRemoteViewThread::Open(const TDesC& aViewName, const TDesC& aThreadName)
+	{
+	// Thread1: create semaphone for communicating w/Thread 2
+	iDbReady = EFalse;
+	
+	//create semphore for communicating between threads:
+	TInt err = iSemaphore.CreateGlobal(aViewName, 0);	
+	if(err != KErrNone)
+		{
+		return err;
+		}
+		
+	// this thread: Start Other thread
+	TheTest.Printf(_L("Starting view thread %S"), &aThreadName);
+	TThreadId id = CViewer::Create(aThreadName, aViewName);
+		
+	RThread thread;
+	err = thread.Open(id);
+	if(err==KErrNone)
+		{	// tell kernel that this thread wants to know when the other thread dies
+		TRequestStatus* stat = &iStatus;
+		User::RequestComplete(stat,KRequestPending);
+		thread.Logon(iStatus);
+		thread.Close();
+		}
+	
+	return err;
+	}
+
+/** Sit and wait until the thread is ready*/
+void RRemoteViewThread::WaitUntilDbIsReady()
+	{
+	if(iDbReady)
+		{
+		return;
+		}
+	iSemaphore.Wait();
+	iDbReady = ETrue;
+	}
+
+/** Always call WaitUntilDbIsReady after this returns a true result
+@return true if the thread is ready, false otherwise*/
+TBool RRemoteViewThread::CheckIfDbIsReady()
+	{
+	if(iDbReady)
+		{
+		return ETrue;
+		}
+	iDbReady = ETrue;
+	return iDbReady;
+	}
+
+/** Signal to the other thread that it should shut down and then wait for it to signal that is has closed 
+*/
+TInt RRemoteViewThread::Close()
+	{
+	if(!iDbReady)
+		{
+		WaitUntilDbIsReady();
+		}
+	iSemaphore.Signal();		// tell thread one it can die now
+	TheTest.Printf(_L("Waiting for other thread to die"));
+	// iStatus is set when the other thread dies
+	User::WaitForRequest(iStatus);
+	iSemaphore.Close();
+	return iStatus.Int();
+	}
+
+//
+//#pragma mark ==== Test Function Prototypes ====
+//
+void PopulateTestDatabaseL(CContactDatabase& aDatabase);
+void TestL();
+void doMainL();
+
+//
+//#pragma mark ==== Test Function Implementations ====
+//
+const TInt KPrintFreq=10;
+
+/** add aCount random contacts to the open database. The 1st item will always be the own card */
+CContactIdArray* PopulateTestDatabaseLC(CContactDatabase& aDatabase, TInt aCount )
+	{
+	__ASSERT_ALWAYS(aCount>0, User::Invariant());
+	CContactIdArray* contacts = CContactIdArray::NewLC();
+	CRandomContactGenerator* generator = CRandomContactGenerator::NewL();
+	CleanupStack::PushL( generator );
+	generator->SetDbL(aDatabase);
+
+	TInt bit = CContactDatabase::ESmsable;
+	
+	TContactItemId itemId = generator->AddTypicalContactForFilterL(bit);
+	TInt counter = 0;
+	TheTest.Printf(_L("Adding own card %d\n"), counter++);
+	CContactItem* ownerCardItem = aDatabase.ReadContactL( itemId );
+	CleanupStack::PushL( ownerCardItem );
+	aDatabase.SetOwnCardL( *ownerCardItem );
+	contacts->AddL(itemId);
+	for(;counter<aCount;counter++)
+		{
+		itemId = generator->AddTypicalContactForFilterL(bit);
+		if(counter%KPrintFreq==0)
+			{
+			TheTest.Printf(_L("Added contact %d: %i\n"), counter, itemId);
+			}
+		contacts->AddL(itemId);
+		}
+	CleanupStack::PopAndDestroy( ownerCardItem ); 
+	CleanupStack::PopAndDestroy( generator );
+	return contacts;
+	}
+
+
+/** Test that the view thread always correctly knows weather or not the view is ready for use*/
+void Test1L()
+	{
+	// Thread 1: Create db
+	CContactDatabase* database = CContactDatabase::ReplaceL();
+	CleanupStack::PushL( database );
+ 
+	// Thread1: create and start a thread with a remote view
+	RRemoteViewThread viewer;
+	User::LeaveIfError(viewer.Open(KViewNameOne, KThreadNameOne));
+	CleanupClosePushL(viewer);
+
+	// Thread 2: Open database
+	// Thread 2: Create named view
+
+	viewer.WaitUntilDbIsReady(); // wait until we've at least got the Db open (can cause file sharing trouble otherwise)
+
+	// Thread 1: add a whole lot of contacts
+	CContactIdArray& contacts = *PopulateTestDatabaseLC(*database, KNumberOfContacts );
+	// Thread 2: print all the contacts in the view
+
+	TInt64 seed=85204;
+	CContactIdArray* toDelete = CContactIdArray::NewLC();
+	TInt i;
+	TInt end = contacts.Count();
+	TInt start = Math::Rand(seed)%end;
+	if(start+KNumberToDeleteAtOneTime<end)
+		{
+		end = start+KNumberToDeleteAtOneTime;
+		}
+	TheTest.Printf(_L("Deleting contatcs %i -- %i\n"), contacts[start],contacts[end-1]);
+	for(i=start;i<end;i++)
+		{
+		toDelete->AddL(contacts[i]);
+		}
+	contacts.Remove(start,end-start);
+	database->DeleteContactsL(*toDelete);
+	toDelete->Reset();
+	
+	CleanupStack::PopAndDestroy(toDelete );	
+	CleanupStack::PopAndDestroy(&contacts );
+	CleanupStack::Pop( );	
+	TheTest( viewer.Close()  == KErrNone );
+	
+	CleanupStack::PopAndDestroy(database );
+
+	}
+
+/** Test to make sure another thread can open the db when this one is performing many operations on it */
+void Test2L()
+	{
+	// Thread 1: Create db
+	CContactDatabase* database = CContactDatabase::ReplaceL();
+	CleanupStack::PushL( database );
+
+ 	// Thread 1: add a whole lot of contacts
+	CContactIdArray& contacts = *PopulateTestDatabaseLC(*database, KNumberOfContactsForTest2 );
+
+
+ 	// Thread 1: create a group
+	CContactItem* group = database->CreateContactGroupL(_L("My Group"));
+	CleanupStack::PushL(group);
+	TContactItemId groupId = group->Id();
+
+	// Thread1: create and start a thread with a remote view
+	RRemoteViewThread viewer;
+	User::LeaveIfError(viewer.Open(KViewNameOne, KThreadNameOne));
+	CleanupClosePushL(viewer);
+
+	// Thread 1: start hammering the database
+	TInt i;
+	TInt err;
+	for(i=-1/*contacts.Count()-1*/;i>=0;i--) 
+		{
+		TRAP(err,database->AddContactToGroupL(contacts[i],groupId));
+		if(err==KErrNotReady || err==KErrLocked)
+			{
+			i++;		// try again
+			}
+		else 
+			{
+			User::LeaveIfError(err);
+			if(i%KPrintFreq==0)
+				{
+				TheTest.Printf(_L("Added contact %d to group\n"),contacts[i]);
+				viewer.CheckIfDbIsReady(); // acknowledge db-is-ready signal
+				}
+			}
+		}
+
+	// Thread 2: Open database
+	// Thread 2: Create named view
+
+	viewer.WaitUntilDbIsReady(); // just in case is not ready yet -- acknowledge db-is-ready signal
+	
+	// Thread 2: print all the contacts in the view
+
+	CContactIdArray* toDelete = CContactIdArray::NewLC();
+	TInt64 seed=85204;
+	TInt end   = contacts.Count();
+	TInt start = Math::Rand(seed)%end;
+	if(start+KNumberToDeleteAtOneTime<end)
+		{
+		end = start+KNumberToDeleteAtOneTime;
+		}
+	TheTest.Printf(_L("\nDeleting contacts %i -- %i\n"), contacts[start],contacts[end-1]);
+	for(i=start;i<end;i++)
+		{
+		toDelete->AddL(contacts[i]);
+		}
+	contacts.Remove(start,end-start);
+	database->DeleteContactsL(*toDelete);
+	toDelete->Reset();
+	
+	CleanupStack::PopAndDestroy(toDelete );	
+	CleanupStack::Pop( );	
+	TheTest( viewer.Close()  == KErrNone );
+	
+	CleanupStack::PopAndDestroy(group );
+	CleanupStack::PopAndDestroy(&contacts );
+	CleanupStack::PopAndDestroy(database );
+	}
+
+
+//
+// -------> Static global functions (source)
+//
+void doMainL()
+	{
+	CActiveScheduler*  scheduler = new (ELeave) CActiveScheduler;
+	CleanupStack::PushL(scheduler);
+	CActiveScheduler::Install(scheduler);
+
+	// This call is just included to get rid of a linker warning LNK4089
+	// Probably the reason for the warning is that the MVC++ environment 
+	// sometimes does not realise when a library is actually used.
+	// We do not care about if the server is hung or not, we just want to 
+	// make the call.
+
+	// Delete any existing ini file so that we can be sure this test is ok
+	Test1L();			// Test read-when-not-ready Panic defect
+
+	Test2L();
+	CleanupStack::PopAndDestroy( scheduler );
+	// delete db file here
+	}
+
+
+/**
+
+@SYMTestCaseID     PIM-T-REMOTEVIEW-0001
+
+*/
+
+GLDEF_C TInt E32Main()
+	{
+	RDebug::Print(_L("t_bench started"));
+
+	CTrapCleanup* cleanupStack = NULL;
+	__UHEAP_MARK;
+	TheTest.Start(_L("@SYMTESTCaseID:PIM-T-REMOTEVIEW-0001 Remote View testing"));
+
+	TheTest.Title();
+	cleanupStack = CTrapCleanup::New();
+	TRAPD(ret, doMainL());
+	TheTest(ret == KErrNone);
+	delete cleanupStack;
+
+	TheTest.End();
+	TheTest.Close();
+	__UHEAP_MARKEND;
+	return(KErrNone);
+	}