// APFCTLF.CPP
//
// Copyright (c) 1997-1999 Symbian Ltd.  All rights reserved.
//

#include <f32file.h>
#include <apadef.h>
#include <apfctlf.h>
#include <apfdef.h>
#include <apgctl.h>
#include "apfstd.h" // Panics etc.

_LIT(KSysControlDllSearchPath,"\\System\\Controls\\");
_LIT(KSysControlDllSearchString,"*\\System\\Controls\\*");
_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;


/////////////////////////////
// CApaScanningControlFinder
/////////////////////////////

EXPORT_C CApaScanningControlFinder* CApaScanningControlFinder::NewL(const RFs& aFs)
	{
	CApaScanningControlFinder* self=new(ELeave) CApaScanningControlFinder(aFs);
	return self;
	}


EXPORT_C CApaScanningControlFinder* CApaScanningControlFinder::NewLC(const RFs& aFs)
	{
	CApaScanningControlFinder* self=CApaScanningControlFinder::NewL(aFs);
	CleanupStack::PushL(self);
	return self;
	}


CApaScanningControlFinder::CApaScanningControlFinder(const RFs& aFs)
:iFs(aFs)
	{__DECLARE_NAME(_S("CApaScanningControlFinder"));}


EXPORT_C CApaScanningControlFinder::~CApaScanningControlFinder()
	{
	delete iDriveList;
	}


EXPORT_C TFileName CApaScanningControlFinder::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 (.CTL 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:
//
	{
	// check that aFileName isn't wild
	TParsePtrC ptr(aFileName);
	if (ptr.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(KSysControlDllSearchString); 
	if (error!=KErrNotFound && ptr.DrivePresent())
		{// 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 CApaScanningControlFinder::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 CApaScanningControlFinder::ScanDriveByNameL(TFileName& aFullPath,const TDesC& aDriveName,const TDesC& aTargetName)
	{
	// check the given file path on the current drive
	TParsePtrC target(aTargetName);
	TParse parser;
	TPtrC name=target.NameAndExt();
	TInt error = parser.Set(aDriveName,&KSysControlDllSearchPath,&name);
	if (error!=KErrNone)
		return EFalse;
	TEntry entry;
	error = iFs.Entry(parser.FullName(),entry);
	CheckErrorL(error);
	if (error==KErrNone)
		{
		aFullPath = parser.FullName();
		return ETrue;
		}
	aFullPath.SetLength(0);
	return EFalse;
	}


TBool CApaScanningControlFinder::ScanDriveByUidL(TFileName& aFullName,TUid aUid,const TDesC& /*aExtension*/,const TDesC& aDriveName)
	{
	// get a list of all dirs below \system\controls\...
	CDir* fileList=NULL;
	CDir* dirList=NULL;
	TParse parser;
	User::LeaveIfError(parser.Set(aDriveName,&KSysControlDllSearchPath,NULL));
	TInt error=iFs.GetDir(parser.DriveAndPath(),KEntryAttAllowUid,ESortNone,fileList,dirList);
	CheckErrorL(error);
	delete dirList;
	// check the files
	if (error==KErrNone)
		{
		for (TInt i=0 ; i<fileList->Count() ; i++)
			{
			error = KErrNone;
			const TEntry& entry=(*fileList)[i];
			if (entry.iType[1]==KUidSystemControlDll && entry.iType[2]==aUid)
				{// we've found one
				TParsePtrC ptr(entry.iName);
				error = parser.Set(ptr.FullName(),&aDriveName,&KSysControlDllSearchPath);
				if (error!=KErrNone)
					continue;
				aFullName = parser.FullName();
				delete fileList;
				return ETrue;
				}
			}
		}
	delete fileList;
	return EFalse;
	}


EXPORT_C void CApaScanningControlFinder::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 = new(ELeave) TDriveList;
	User::LeaveIfError(iFs.DriveList(*iDriveList));
	}


EXPORT_C TBool CApaScanningControlFinder::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
		iFileIndex=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 (iFileIndex<iFileList->Count())
		{
		if (GetAppEntryL(aEntry,(*iFileList)[iFileIndex++])==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 CApaScanningControlFinder::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("*"),&KSysControlDllSearchPath,&name);
		ret = iFs.GetDir(parse.FullName(),KEntryAttAllowUid,ESortNone,iFileList);
		}
	CheckErrorL(ret);
	return ret;
	}


TInt CApaScanningControlFinder::GetAppEntryL(TApaAppEntry& aAppEntry,const TEntry& aEntry)
// looks for /system/apps/dir/dir.app
// returns KErrNone and sets aAppEntry if found
// only leaves KErrNoMemory
	{
	TInt error = KErrNotFound;
	TParse parser;
	if (aEntry.iType[1]==KUidSystemControlDll)
		{// we've found one
		TParsePtrC ptr(aEntry.iName);
		TDriveUnit drive(iDriveNum);
		TDriveName name=drive.Name();
		error = parser.Set(ptr.FullName(),&name,&KSysControlDllSearchPath);
		if (error!=KErrNone)
			{
			CheckErrorL(error);
			return error;
			}
		aAppEntry.iUidType = aEntry.iType;
		aAppEntry.iFullName = parser.FullName();
		}
	return error;
	}


EXPORT_C TFileName CApaScanningControlFinder::TempPath()const
	{
	return TFileName(Apfile::TempPath());
	}


EXPORT_C TFileName CApaScanningControlFinder::DefaultAppInfoFileName()const
	{
	return TFileName(KApfDefaultAppInfoFileFullName);
	}


void CApaScanningControlFinder::CheckErrorL(TInt aError)
// static
	{
	if (aError!=KErrNone && aError!=KErrNotFound && aError!=KErrPathNotFound && aError!=KErrNotReady
		&& aError!=KErrDisMounted && aError!=KErrCorrupt && aError!=KErrNotSupported)
		{
		User::Leave(aError);
		}
	}
