// APGAPLST.CPP
//
// Copyright (c) 1997-1999 Symbian Ltd.  All rights reserved.
//

#include "apgaplst.h"
#include "apgicnfl.h" 
#include "apgstd.h" 
#include "apfdef.h"
#include "..\apparc\trace.h"

// Delays in the pseudo idle object that builds the application list
//

const TInt KIdleStartDelay=0;
const TInt KIdlePeriodicDelay=50000; // 0.05 of a second

//
// Class CApaAppData
//

CApaAppData* CApaAppData::NewL(const TApaAppEntry& aAppEntry,const TDesC& aDefaultAppInfoFileName,const RFs& aFs)
	{
	CApaAppData* self=new(ELeave) CApaAppData();
	CleanupStack::PushL(self);
	self->ConstructL(aAppEntry,aDefaultAppInfoFileName,aFs);
	CleanupStack::Pop(); // self
	return self;
	}


CApaAppData::CApaAppData()
	:iIsPresent(CApaAppData::EIsPresent),
	iDataTypeArray(5)
	{}


void CApaAppData::ConstructL(const TApaAppEntry& aAppEntry,const TDesC& aDefaultAppInfoFileName,const RFs& aFs)
	{
	__DECLARE_NAME(_S("CApaAppData"));
	//
	// store entry data
	iUidType = aAppEntry.iUidType;
	iFullName = aAppEntry.iFullName.AllocL();
	//
	// initialize capability
	iCapabilityBuf.FillZ(iCapabilityBuf.MaxLength());
	//
	// open AppInfo file
	GetAifDataL(aFs,aDefaultAppInfoFileName);
	}


void CApaAppData::GetAifDataL(const RFs& aFs,const TDesC& aDefaultAppInfoFileName)
	{
	TParse parse;
	parse.Set(KAppInfoFileExtension,iFullName,NULL);
	CApaAppInfoFileReader* appInfoFile=NULL;
	TRAPD(ret, appInfoFile=CApaAppInfoFileReader::NewL(parse.FullName(),iUidType[2]));
	//
	// use the appInfo file if it opened successfully
	TBool useDefaultCaption=EFalse;
	TBool useDefaultIcons=EFalse;
	if (appInfoFile!=NULL)
		{
		// set the timestamp 
		TEntry entry;
		ret=aFs.Entry(parse.FullName(),entry);
		if (ret!=KErrNone)
			{
			delete appInfoFile;
			User::Leave(ret);
			}
		iAifTimeStamp = entry.iModified;
		//
		// read the data
		appInfoFile->Capability(iCapabilityBuf);
		delete iCaption;
		iCaption = NULL;
		TRAP(ret, iCaption=appInfoFile->CaptionL(User::Language()).AllocL());
		if (ret!=KErrNone)
			useDefaultCaption = ETrue;
		TRAP(ret, GetIconsL(*appInfoFile));
		if (ret!=KErrNone)
			useDefaultIcons = ETrue;
		if (iDataTypeArray.Count()>0)
			iDataTypeArray.Delete(0,iDataTypeArray.Count());
		TRAP(ret,appInfoFile->DataTypesSupportedL(iDataTypeArray));
		if (ret!=KErrNone)
			{
			if (iDataTypeArray.Count()>0)
				iDataTypeArray.Delete(0,iDataTypeArray.Count());
			}
		delete appInfoFile;
		appInfoFile = NULL;
		}
	else 
		{ // icon file could not be opened
		iAifTimeStamp = 0; 
		useDefaultCaption = ETrue;
		useDefaultIcons = ETrue;
		}
	//
	// use the default icon file for any bits not in the aps icon file
	if (useDefaultCaption)
		{
		iCaption = parse.Name().AllocL();
		}
	if (useDefaultIcons)
		{
		CApaAppInfoFileReader* defaultAppInfoFile = CApaAppInfoFileReader::NewLC(aDefaultAppInfoFileName);
		GetIconsL(*defaultAppInfoFile);
		CleanupStack::PopAndDestroy(); // defaultAppInfoFile
		}	
	}


void CApaAppData::GetIconsL(CApaAppInfoFileReader& aAppInfoFile)
	{
	delete iIcon[0];
	delete iIcon[1];
	delete iIcon[2];
	iIcon[0] = NULL;
	iIcon[1] = NULL;
	iIcon[2] = NULL;	
	iIcon[0] = aAppInfoFile.CreateMaskedBitmapL(24);
	iIcon[1] = aAppInfoFile.CreateMaskedBitmapL(32);
	iIcon[2] = aAppInfoFile.CreateMaskedBitmapL(48);
	}


CApaAppData::~CApaAppData()
// Just delete components, NOT iNext (next CApaAppData in the list).
	{
	delete iSuccessor;
	delete iCaption;
	delete iFullName;
	for (TInt i=0;i<KApaMaxAppIcons;i++)
		delete iIcon[i];
	}


EXPORT_C TApaAppEntry CApaAppData::AppEntry() const
	{
	return TApaAppEntry(iUidType,*iFullName);
	}


EXPORT_C void CApaAppData::Capability(TDes8& aCapabilityBuf)const
	{
	TApaAppCapability::CopyCapability(aCapabilityBuf,iCapabilityBuf);
	}

TBool CApaAppData::UpdateAif(const RFs& aFs,const TDesC& aDefaultAppInfoFileName)
// returns true if changes were made to the cached data
	{
	__APA_PROFILE_START(17);
	// check date stamp on AIF file
	TBool changed=EFalse;
	TEntry entry;
	TParse parse;
	parse.Set(KAppInfoFileExtension,iFullName,NULL);
	TInt ret=aFs.Entry(parse.FullName(),entry);
	if ((ret==KErrNotFound && iAifTimeStamp!=TTime(0)) || (ret==KErrNone && entry.iModified!=iAifTimeStamp))
		{// AIF has disappeared or changed - re-read data
		GetAifDataL(aFs,aDefaultAppInfoFileName);
		changed = ETrue;
		}
	__APA_PROFILE_END(17);
	return changed;
	}

EXPORT_C TDataTypePriority CApaAppData::DataType(const TDataType& aDataType) const
// returns the priority of the data type
	{
	TInt count=iDataTypeArray.Count();
	for (TInt i=0; i<count; i++)
		{
		if (iDataTypeArray[i].iDataType==aDataType)
			return iDataTypeArray[i].iPriority;
		}
	return KDataTypePriorityNotSupported;
	}


TBool CApaAppData::IsPending()const
	{
	return (iIsPresent==CApaAppData::EPresentPendingUpdate 
		|| iIsPresent==CApaAppData::ENotPresentPendingUpdate);
	}


//
// Class CApaAppList
//

EXPORT_C CApaAppList* CApaAppList::NewL(RFs& aFs,CApaAppFinder* aAppFinder)
	{
	CApaAppList* self=new CApaAppList(aFs,aAppFinder);
	if (!self)
		{
		delete aAppFinder;
		User::LeaveNoMemory();
		}
	return self;
	}

CApaAppList::CApaAppList(RFs& aFs,CApaAppFinder* aAppFinder)
:iFs(aFs),
iAppFinder(aAppFinder)
	{__DECLARE_NAME(_S("CApaAppList"));}

EXPORT_C CApaAppList::~CApaAppList()
	{
	CApaAppData* appData=iAppData;
	while (appData)
		{
		CApaAppData* next=appData->iNext;
		delete appData;
		appData=next;
		}
	delete iAppFinder;
	delete iIdler;
	}

EXPORT_C void CApaAppList::StartIdleUpdateL()
// begin an idle object to do the UpdateL
	{
	SetPending(iAppData);
	if (iIdler)
		{
		delete iIdler;
		iIdler=NULL;
		}
	iIdler=CPeriodic::NewL(CActive::EPriorityStandard);
	SetPending(iAppData);
	iCurrentApp=TApaAppEntry();
	iAppFinder->FindAllAppsL();

	iIdler->Start(KIdleStartDelay,KIdlePeriodicDelay,TCallBack(IdleUpdateCallback,this));
	}

TInt CApaAppList::IdleUpdateCallback(TAny* aObject)
	{
	CApaAppList* self=REINTERPRET_CAST(CApaAppList*,aObject);
	TBool end=self->IdleUpdate();
	if (end==EFalse)
		self->StopIdler();
	return end;
	}

void CApaAppList::StopIdler()
	{
	delete iIdler;
	iIdler=NULL;
	}

TInt CApaAppList::IdleUpdate()
// returns ETrue if there is more scanning to be done.
// this CAN'T leave
	{
	TBool more=EFalse;
	TRAPD(err,more=iAppFinder->NextL(iCurrentApp));
	if (err!=KErrNone)
		return more;
	TBool hasChanged=EFalse;
	if (more)
		{
		TRAPD(err,UpdateNextAppL(iCurrentApp,hasChanged));
		if (err!=KErrNone)
			{
			SetNotFound(iAppData,hasChanged);
			more=EFalse; // abandon ship
			}
		}
	else
		{
		SetNotFound(iAppData,hasChanged);
		Purge();
		}
	//
	if (hasChanged)
		iUpdateCounter++;
	return more;
	}

EXPORT_C void CApaAppList::UpdateL()
// Scan all drives for available apps
// For each app, check if present in list. 
// Apps in the list can be overridden by apps appearing on earlier drives in the search order.
// Such apps are marked as superseded, and actually swapped as part of the Purge().
// If an app is not in the list a new CApaAppData is added.
// Increment iUpdateCounter if list has changed
// If an error occurs, the list will be incomplete but functional.
	{
	SetPending(iAppData);
	//
	// scan for apps
	TBool hasChanged=EFalse;
	iAppFinder->FindAllAppsL();
	TApaAppEntry appEntry;

	__APA_PROFILE_START(16);
	while (iAppFinder->NextL(appEntry))
		UpdateNextAppL(appEntry,hasChanged);
	__APA_PROFILE_END(16);

	SetNotFound(iAppData,hasChanged);
	//
	if (hasChanged)
		iUpdateCounter++;
	}

void CApaAppList::SetPending(CApaAppData* aAppData)
	// set all apps to pending update - we'll find them again as we scan
	{
	while (aAppData)
		{
		if (aAppData->iIsPresent==CApaAppData::ENotPresent 
			|| aAppData->iIsPresent==CApaAppData::ENotPresentPendingUpdate)
			{
			aAppData->iIsPresent = CApaAppData::ENotPresentPendingUpdate;
			}
		else
			aAppData->iIsPresent = CApaAppData::EPresentPendingUpdate;
		aAppData = aAppData->iNext;
		}
	}

void CApaAppList::SetNotFound(CApaAppData* aAppData, TBool& aHasChanged)
	// mark any unfound apps not present
	{
	while (aAppData)
		{
		if (aAppData->IsPending())
			{
			aAppData->iIsPresent = CApaAppData::ENotPresent;
			aHasChanged = ETrue;
			}
		aAppData = aAppData->iNext;
		}
	}

void CApaAppList::UpdateNextAppL(TApaAppEntry& aAppEntry,TBool& aHasChanged)
	{
	CApaAppData* appData=AppDataByUid(aAppEntry.iUidType[2]);
	if (appData==NULL)
		{// not in list, so add it at the start
		TRAPD(err,appData=CApaAppData::NewL(aAppEntry,iAppFinder->DefaultAppInfoFileName(),iFs));
		if (err==KErrNone)
			{
			appData->iNext=iAppData;
			iAppData=appData;
			aHasChanged=ETrue;
			}
		}
	else if (appData->IsPending())
		{// not already found - we made need to override this one
		if (aAppEntry.iFullName.CompareF(*appData->iFullName)!=0)
			{
			delete appData->iSuccessor;
			appData->iSuccessor = NULL;
			appData->iSuccessor = new(ELeave) TApaAppEntry();
			*appData->iSuccessor = aAppEntry;

			// REM kludge to ensure that correct dll is used even if locks are not released.
			HBufC* oldName=appData->iFullName;
			appData->iFullName = NULL;
			appData->iFullName = aAppEntry.iFullName.Alloc();
			if (!appData->iFullName)
				{
				appData->iFullName = oldName;
				User::LeaveNoMemory();
				}
			delete oldName;
			// REM kludge ends.

			appData->iIsPresent = CApaAppData::ESuperseded;
			aHasChanged=ETrue;
			}
		else
			{
			if (appData->UpdateAif(iFs,iAppFinder->DefaultAppInfoFileName()) 
				|| appData->iIsPresent==CApaAppData::ENotPresentPendingUpdate) 
				{
				aHasChanged=ETrue; 
				}
			appData->iIsPresent = CApaAppData::EIsPresent;
			}
		}
	}

EXPORT_C void CApaAppList::Purge()
// remove apps from the list if (1) they're no longer present on the machine and (2) no-one's got a lock on them
// update apps if (1) they have been superseded and (2) no-one's got a lock on them
//
	{
	CApaAppData* appData=iAppData;
	CApaAppData* prev=NULL;
	while (appData)
		{
		CApaAppData* next=appData->iNext;
		if (appData->iIsPresent==CApaAppData::ENotPresent && appData->iReferenceCount==0)
			{
			if (prev)
				prev->iNext=next;
			else
				iAppData=next;
			delete appData;
			}
		else if (appData->iIsPresent==CApaAppData::ESuperseded && appData->iReferenceCount==0)
			{
			CApaAppData* newApp=NULL;
			TRAPD(err,newApp=CApaAppData::NewL(*appData->iSuccessor,iAppFinder->DefaultAppInfoFileName(),iFs));
			if (err==KErrNone)
				{
				// remove the old one and add the new one in its place
				if (prev)
					prev->iNext=newApp;
				else
					iAppData=newApp;
				newApp->iNext = appData->iNext;
				delete appData;
				// increment the iterator
				prev = newApp;
				}
			}
		else
			prev=appData;
		appData=next;
		}
	}

EXPORT_C TInt CApaAppList::Count() const
	{
	TInt count=0;
	CApaAppData* appData=iAppData;
	while (appData)
		{
		count++;
		appData=appData->iNext;
		}
	return count;
	}

EXPORT_C CApaAppData* CApaAppList::FirstApp() const
	{
	return iAppData;
	}

EXPORT_C CApaAppData* CApaAppList::NextApp(const CApaAppData* aApp) const
	{
	__ASSERT_ALWAYS(aApp,Panic(EPanicNoAppDataSupplied));
	//
	return aApp->iNext;
	}

EXPORT_C CApaAppData* CApaAppList::AppDataByUid(TUid aAppUid) const
// Return NULL if uid is not in list
	{
	if (aAppUid==KNullUid)
		return NULL; // never match null UID as it represents an un-UIDed file
	CApaAppData* appData=iAppData;
	while (appData)
		{
		if (appData->AppEntry().iUidType[2]==aAppUid)
			return appData;
		appData=appData->iNext;
		}
	return NULL;
	}

EXPORT_C TInt CApaAppList::UpdateCounter() const
	{
	return iUpdateCounter;
	}

EXPORT_C TUid CApaAppList::PreferredDataHandlerL(const TDataType& aDataType) const
// find the preferred "default" handler of a data type
// returns a null uid if not found
	{
	CApaAppData* appData=iAppData;
	TInt topPriority=KDataTypePriorityNotSupported;
	TUid uid={0};
	while (appData)
		{
		TInt priority=appData->DataType(aDataType);
		if (priority>topPriority)
			{
			topPriority=priority;
			uid=appData->AppEntry().iUidType[2];
			}
		appData=appData->iNext;
		}
	return uid;
	}
