/*
	File:		IterateDirectory.c

	Contains:	File Manager directory iterator routines.

	Version:	MoreFiles

	Copyright:	© 1995-2001 by Jim Luther and Apple Computer, Inc., all rights reserved.

	You may incorporate this sample code into your applications without
	restriction, though the sample code has been provided "AS IS" and the
	responsibility for its operation is 100% yours.  However, what you are
	not permitted to do is to redistribute the source as "DSC Sample Code"
	after having made changes. If you're going to re-distribute the source,
	we require that you make it clear in the source that the code was
	descended from Apple Sample Code, but that you've made changes.

	File Ownership:

		DRI:				Apple Macintosh Developer Technical Support

		Other Contact:		Apple Macintosh Developer Technical Support
							<http://developer.apple.com/bugreporter/>

		Technology:			DTS Sample Code

	Writers:

		(JL)	Jim Luther

	Change History (most recent first):

		 <2>	  2/7/01	JL		Added standard header. Updated names of includes.
		<1>		12/06/99	JL		MoreFiles 1.5.
*/

#include <MacTypes.h>
#include <MacErrors.h>
#include <Files.h>

#define	__COMPILINGMOREFILES

#include "MoreFilesExtras.h"
#include "IterateDirectory.h"

/*
**	Type definitions
*/

/* The IterateGlobals structure is used to minimize the amount of
** stack space used when recursively calling IterateDirectoryLevel
** and to hold global information that might be needed at any time.
*/
#if PRAGMA_STRUCT_ALIGN
#pragma options align=mac68k
#endif
struct IterateGlobals
{
	IterateFilterProcPtr	iterateFilter;	/* pointer to IterateFilterProc */
	CInfoPBRec				cPB;			/* the parameter block used for PBGetCatInfo calls */
	Str63					itemName;		/* the name of the current item */
	OSErr					result;			/* temporary holder of results - saves 2 bytes of stack each level */
	Boolean					quitFlag;		/* set to true if filter wants to kill interation */
	unsigned short			maxLevels;		/* Maximum levels to iterate through */
	unsigned short			currentLevel;	/* The current level IterateLevel is on */
	void					*yourDataPtr;	/* A pointer to caller data the filter may need to access */
};
#if PRAGMA_STRUCT_ALIGN
#pragma options align=reset
#endif

typedef struct IterateGlobals IterateGlobals;
typedef IterateGlobals *IterateGlobalsPtr;

/*****************************************************************************/

/*	Static Prototype */

static	void	IterateDirectoryLevel(long dirID,
									  IterateGlobals *theGlobals);

/*****************************************************************************/

/*
**	Functions
*/

static	void	IterateDirectoryLevel(long dirID,
									  IterateGlobals *theGlobals)
{
	if ( (theGlobals->maxLevels == 0) ||						/* if maxLevels is zero, we aren't checking levels */
		 (theGlobals->currentLevel < theGlobals->maxLevels) )	/* if currentLevel < maxLevels, look at this level */
	{
		short index = 1;
		
		++theGlobals->currentLevel;	/* go to next level */
		
		do
		{	/* Isn't C great... What I'd give for a "WITH theGlobals DO" about now... */
		
			/* Get next source item at the current directory level */
			
			theGlobals->cPB.dirInfo.ioFDirIndex = index;
			theGlobals->cPB.dirInfo.ioDrDirID = dirID;
			theGlobals->result = PBGetCatInfoSync((CInfoPBPtr)&theGlobals->cPB);		
	
			if ( theGlobals->result == noErr )
			{
				/* Call the IterateFilterProc */
				CallIterateFilterProc(theGlobals->iterateFilter, &theGlobals->cPB, &theGlobals->quitFlag, theGlobals->yourDataPtr);
				
				/* Is it a directory? */
				if ( (theGlobals->cPB.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
				{
					/* We have a directory */
					if ( !theGlobals->quitFlag )
					{
						/* Dive again if the IterateFilterProc didn't say "quit" */
						IterateDirectoryLevel(theGlobals->cPB.dirInfo.ioDrDirID, theGlobals);
					}
				}
			}
			
			++index; /* prepare to get next item */
		} while ( (theGlobals->result == noErr) && (!theGlobals->quitFlag) ); /* time to fall back a level? */
		
		if ( (theGlobals->result == fnfErr) ||	/* fnfErr is OK - it only means we hit the end of this level */
			 (theGlobals->result == afpAccessDenied) ) /* afpAccessDenied is OK, too - it only means we cannot see inside a directory */
		{
			theGlobals->result = noErr;
		}
			
		--theGlobals->currentLevel;	/* return to previous level as we leave */
	}
}

/*****************************************************************************/

pascal	OSErr	IterateDirectory(short vRefNum,
								 long dirID,
								 ConstStr255Param name,
								 unsigned short maxLevels,
								 IterateFilterProcPtr iterateFilter,
								 void *yourDataPtr)
{
	IterateGlobals	theGlobals;
	OSErr			result;
	long			theDirID;
	short			theVRefNum;
	Boolean			isDirectory;
	
	/* Make sure there is a IterateFilter */
	if ( iterateFilter != NULL )
	{
		/* Get the real directory ID and make sure it is a directory */
		result = GetDirectoryID(vRefNum, dirID, name, &theDirID, &isDirectory);
		if ( result == noErr )
		{
			if ( isDirectory == true )
			{
				/* Get the real vRefNum */
				result = DetermineVRefNum(name, vRefNum, &theVRefNum);
				if ( result == noErr )
				{
					/* Set up the globals we need to access from the recursive routine. */
					theGlobals.iterateFilter = iterateFilter;
					theGlobals.cPB.hFileInfo.ioNamePtr = (StringPtr)&theGlobals.itemName;
					theGlobals.cPB.hFileInfo.ioVRefNum = theVRefNum;
					theGlobals.itemName[0] = 0;
					theGlobals.result = noErr;
					theGlobals.quitFlag = false;
					theGlobals.maxLevels = maxLevels;
					theGlobals.currentLevel = 0;	/* start at level 0 */
					theGlobals.yourDataPtr = yourDataPtr;
				
					/* Here we go into recursion land... */
					IterateDirectoryLevel(theDirID, &theGlobals);
					
					result = theGlobals.result;	/* set the result */
				}
			}
			else
			{
				result = dirNFErr;	/* a file was passed instead of a directory */
			}
		}
	}
	else
	{
		result = paramErr;	/* iterateFilter was NULL */
	}
	
	return ( result );
}

/*****************************************************************************/

pascal	OSErr	FSpIterateDirectory(const FSSpec *spec,
									unsigned short maxLevels,
									IterateFilterProcPtr iterateFilter,
									void *yourDataPtr)
{
	return ( IterateDirectory(spec->vRefNum, spec->parID, spec->name,
						maxLevels, iterateFilter, yourDataPtr) );
}

/*****************************************************************************/
