// APFFNDR.CPP
//
// Copyright (c) 1997-1999 Symbian Ltd.  All rights reserved.
//

#include <f32file.h>
#include <apadef.h>
#include <apffndr.h>
#include <apfdef.h>
#include "apfstd.h" // Panics etc.

_LIT(KAppDirSearchString,"*\\system\\apps\\*");
_LIT(KApfDefaultAppInfoFileFullName,"Z:\\System\\Data\\Default.aif");

const TInt KStartDrive=24; // "y:" - drives are scanned y:->a: then z:
const TInt KLastDrive=KMaxDrives-1; // "z:" - the last drive to scan
const TInt KFinishedScanning=-2;

/////////////////////////////
// Apfile
/////////////////////////////

_LIT(KApfTempPath,"C:\\System\\Temp\\");

EXPORT_C TPtrC Apfile::TempPath()
	{
	return _L("C:\\System\\Temp\\");
	}



/////////////////////////////
// CApaScanningAppFinder
/////////////////////////////

EXPORT_C CApaScanningAppFinder* CApaScanningAppFinder::NewL(const RFs& aFs)
	{
	CApaScanningAppFinder* self=new(ELeave) CApaScanningAppFinder(aFs);
	return self;
	}


EXPORT_C CApaScanningAppFinder* CApaScanningAppFinder::NewLC(const RFs& aFs)
	{
	CApaScanningAppFinder* self=CApaScanningAppFinder::NewL(aFs);
	CleanupStack::PushL(self);
	return self;
	}


CApaScanningAppFinder::CApaScanningAppFinder(const RFs& aFs)
:iFs(aFs)
	{__DECLARE_NAME(_S("CApaScanningAppFinder"));}


EXPORT_C CApaScanningAppFinder::~CApaScanningAppFinder()
	{
	delete iDriveList;
	delete iFileList;
	}


EXPORT_C TFileName CApaScanningAppFinder::FindAppL(const TDesC& aFileName,TUid aFileUid)
// if a valid full path has been passed in just return that, else...
// Scan all drives for a file matching aFileUid and aFileName's extension (.APP is used if no extension is specified)
// if aFileUid is supplied aFileName is ignored
// if aFileUid is KNullUid a file matching aFileName is searched for
// aFileName must not contain any wildcards
// Leaves with KErrNotFound if no match is found
// Drives are scanned in the order y:->a: then z:
//
	{
	if (aFileUid==KNullUid)
		{
		if (aFileName.Length()==0)
			User::Leave(KErrArgument);
		}
	// check for illegal characters
	TParse parse;
	User::LeaveIfError(parse.Set(aFileName,NULL,NULL));
	// check that aFileName isn't wild
	if (parse.IsWild())
		User::Leave(KErrBadName);
	//
	// see if a valid full path has been passed in (we wont have to scan if it has)
	TEntry entry;
	TInt error=aFileName.MatchF(KAppDirSearchString); 
	if (error!=KErrNotFound)
		{// we might have a full path
		error = iFs.Entry(aFileName,entry);
		if (error==KErrNone && !entry.IsDir() && (aFileUid==KNullUid || entry.MostDerivedUid()==aFileUid))
			// this one exists, so just return it!
			return aFileName;
		}
	//
	TFileName filename;
	TParsePtrC targetName(aFileName);
	// get a list of available drives
	TDriveList driveList;
	User::LeaveIfError(iFs.DriveList(driveList));
	//
	// iterate through the drive list, scanning the path on each drive in turn until a dll is found
	__ASSERT_DEBUG(KStartDrive<KMaxDrives,Panic(EDPanicStartDriveOutOfRange));
	TDriveInfo driveInfo;
	TBool matchFound=EFalse;
	TInt n=KStartDrive;
	while (!matchFound && n!=KFinishedScanning)
		{
		// scan drive n
		if (driveList[n]!=0)
			{//the drive exists
			iFs.Drive(driveInfo,n);
			if (driveInfo.iType!=EMediaNotPresent && driveInfo.iType!=EMediaRemote)
				{
				TDriveUnit drive(n);
				if (aFileUid!=KNullUid)
					matchFound = ScanDriveByUidL(filename,aFileUid,targetName.Ext(),drive.Name());
				else
					matchFound = ScanDriveByNameL(filename,drive.Name(),targetName.NameAndExt());
				}
			}
		// select the next drive to scan
		n = NextDriveToScan(n);
		}
	if (!matchFound)
		User::Leave(KErrNotFound);
	return filename;
	}


TInt CApaScanningAppFinder::NextDriveToScan(TInt aCurrentDrive)const
// applies the scanning order y:->a: then z:
//
	{
	if (aCurrentDrive==KLastDrive)
		return KFinishedScanning;
	else if (aCurrentDrive==0)
		return KLastDrive; // finally scan the last one
	else if (aCurrentDrive>0 && aCurrentDrive<KMaxDrives)
		return aCurrentDrive-1;
	else
		{
		Panic(EDPanicDriveOutOfRange);
		return KErrGeneral; // never gets here, but it wont compile otherwise
		}
	}


TBool CApaScanningAppFinder::ScanDriveByNameL(TFileName& aFullPath,const TDesC& aDriveName,const TDesC& aTargetName)
	{
	// work out the full search path
	SetSearchPathL(aFullPath,aTargetName); // abuse aFullPath to temporarily contain the search path
	// check the given file path on the current drive
	TParse parser;
	TInt error = parser.Set(aDriveName,&aFullPath,NULL);
	if (error!=KErrNone)
		return EFalse;
	TEntry entry;
	error = iFs.Entry(parser.FullName(),entry);
	CheckErrorL(error);
	if (error==KErrNone)
		{
		parser.Set(aDriveName,&entry.iName,&aFullPath);
		aFullPath = parser.FullName();
		return ETrue;
		}
	aFullPath.SetLength(0);
	return EFalse;
	}


TBool CApaScanningAppFinder::ScanDriveByUidL(TFileName& aFullName,TUid aUid,const TDesC& aExtension,const TDesC& aDriveName)
	{
	// get a list of all dirs below \system\apps\...
	CDir* fileList=NULL;
	CDir* dirList=NULL;
	TParse parser;
	User::LeaveIfError(parser.Set(aDriveName,&KAppDllSearchPath,NULL));
	TInt error=iFs.GetDir(parser.DriveAndPath(),KEntryAttMatchExclusive|KEntryAttDir,ESortNone,fileList,dirList);
	CheckErrorL(error);
	delete fileList;
	// try each dir for apps matching given type
	if (error==KErrNone)
		{
		TEntry entry;
		for (TInt i=0 ; i<dirList->Count() ; i++)
			{
			error = KErrNone;
			TParsePtrC ptr((*dirList)[i].iName);
			error = parser.Set(ptr.FullName(),&aExtension,&KAppFileExtension);
			if (error!=KErrNone)
				continue;
			aFullName = parser.FullName(); // abuse aFullName to conserve stack
			error = parser.Set(aDriveName,&KAppDllSearchPath,&aFullName);
			aFullName.SetLength(0); // stop abusing it
			if (error!=KErrNone)
				continue;
			error = parser.AddDir(ptr.Name());
			if (error!=KErrNone)
				continue;
			// see if this file exists
			error = iFs.Entry(parser.FullName(),entry);
			if (error==KErrNone && entry[2]==aUid)
				{
				// if there's a match, fill in aFullName and return
				aFullName = parser.FullName();
				delete dirList;
				return ETrue;
				}
			}
		}
	delete dirList;
	return EFalse;
	}


#pragma warning ( disable : 4100 )
// aSearchPath is unreferenced formal parameter

void CApaScanningAppFinder::SetSearchPathL(TFileName& aSearchPath,const TDesC& aFileName)const
// sets aSearch path to be \system\apps\[aFileName.name]\[aFileName.name].[ext]
// if no extension is specified KAppFileExtension is used instead
//
	{
	TParsePtrC filename(aFileName);
	TParse parser;
	User::LeaveIfError( parser.SetNoWild(filename.NameAndExt(),&KAppFileExtension,&KAppDllSearchPath) );
	// add the "appname" directory
	User::LeaveIfError( parser.AddDir(parser.Name()) ); 
	aSearchPath.SetLength(0);
	aSearchPath.Append(parser.FullName());
	}

#pragma warning ( default : 4100 )


EXPORT_C void CApaScanningAppFinder::FindAllAppsL()
// Sets up the finder for a complete scan for all available apps
// should be followed by multiple calls to NextL()
	{
	iDriveNum = KStartDrive; // reset to first drive to scan
	delete iFileList;
	iFileList = NULL;
	delete iDriveList;
	iDriveList = NULL;
	iDriveList = new(ELeave) TDriveList;
	User::LeaveIfError(iFs.DriveList(*iDriveList));
	}


EXPORT_C TBool CApaScanningAppFinder::NextL(TApaAppEntry& aEntry)
// Should be preceeded by a call to FindAllApps() to initialise, and should then be called multiply in a loop
// Fills in aEntry with an app found and returns ETrue, indicating that there is more scanning to do,
//  or returns EFalse if no app was found and there are no more drives to scan
// Only leaves KErrNoMemory
	{
	__ASSERT_ALWAYS(iDriveList,Panic(EPanicFindAllAppsNotCalled));
	//
	if (iDriveNum==KFinishedScanning)
		{
		delete iFileList;
		iFileList = NULL;
		delete iDriveList;
		iDriveList = NULL;
		return EFalse; // no more drives to scan
		}
	//
	// get a file list if there isn't one
	if (!iFileList)
		{// scan the next drive/dir
		iDirIndex = 0;
		if (GetFileListL(iDriveNum)!=KErrNone)
			{
			iDriveNum = NextDriveToScan(iDriveNum);
			return NextL(aEntry); // eg drive doesn't exist - try the next drive
			}
		}
	//
	// scan the file list
	while (iDirIndex<iFileList->Count())
		{
		if (GetAppEntryL(aEntry,(*iFileList)[iDirIndex++])==KErrNone)
			return ETrue; // we found one
		}
	//
	// current dir has been exhausted
	iDriveNum = NextDriveToScan(iDriveNum);
	delete iFileList;
	iFileList = NULL;
	return NextL(aEntry); // scan the next drive
	}


TInt CApaScanningAppFinder::GetFileListL(TInt aDriveNum)
// gets a dir list for system/apps on the specified drive
// only leaves KErrNoMemory
	{
	if ((*iDriveList)[aDriveNum]==0)
		return KErrDisMounted;
	//
	TDriveInfo driveInfo;
	TInt ret = iFs.Drive(driveInfo,aDriveNum);
	if (ret==KErrNone)
		{
		if (driveInfo.iType==EMediaNotPresent || driveInfo.iType==EMediaRemote)
			return KErrNotFound;
		//
		TDriveUnit driveUnit(aDriveNum);
		TParse parse;
		TDriveName name=driveUnit.Name();
		parse.Set(_L("*"),&KAppDllSearchPath,&name);
		ret = iFs.GetDir(parse.FullName(),KEntryAttMatchExclusive|KEntryAttDir,ESortNone,iFileList);
		}
	CheckErrorL(ret);
	return ret;
	}


TInt CApaScanningAppFinder::GetAppEntryL(TApaAppEntry& aAppEntry,const TEntry& aDir)
// looks for /system/apps/dir/dir.app
// returns KErrNone and sets aAppEntry if found
// only leaves KErrNoMemory
	{
	if (aDir.iName.Length()>KApaMaxAppFileName)
		return KErrBadName;
	//
	TDriveUnit drive(iDriveNum);
	TParse parse;
	TInt ret = parse.SetNoWild(aDir.iName,NULL,NULL);
	__ASSERT_DEBUG(ret==KErrNone, Panic(EDPanicBadDirName) );
	//
	SetSearchPathL(aAppEntry.iFullName,parse.Name()); // abuse aAppEntry.iFullName to conserve stack
	TDriveName name=drive.Name();
	parse.Set(aAppEntry.iFullName,&name,NULL);
	aAppEntry.iFullName.SetLength(0); // reset it
	//
	TEntry entry;
	ret=iFs.Entry(parse.FullName(),entry);
	if (ret==KErrNone)
		{
		aAppEntry.iUidType = entry.iType;
		aAppEntry.iFullName = parse.FullName();
		}
	else 
		CheckErrorL(ret);
	//
	return ret;
	}


EXPORT_C TFileName CApaScanningAppFinder::TempPath()const
	{
	return TFileName(KApfTempPath);
	}


EXPORT_C TFileName CApaScanningAppFinder::DefaultAppInfoFileName()const
	{
	return TFileName(KApfDefaultAppInfoFileFullName);
	}


void CApaScanningAppFinder::CheckErrorL(TInt aError)
// static
	{
	if (aError!=KErrNone && aError!=KErrNotFound && aError!=KErrPathNotFound && aError!=KErrNotReady
		&& aError!=KErrDisMounted && aError!=KErrCorrupt && aError!=KErrNotSupported)
		{
		User::Leave(aError);
		}
	}
