diff -r d4f567ce2e7c -r 5b6f26637ad3 phonebookengines_old/contactsmodel/tsrc/T_RemoteView.cpp --- /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 +#include +#include +#include +#include + +// System includes +#include +#include +#include +#include + +// 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(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(;counterAddTypicalContactForFilterL(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+KNumberToDeleteAtOneTimeAddL(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+KNumberToDeleteAtOneTimeAddL(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); + }