gecko-dev/lib/mac/MoreFiles/MoreFilesSearch.c
1998-03-28 02:44:41 +00:00

1231 lines
36 KiB
C

/*
** Apple Macintosh Developer Technical Support
**
** IndexedSearch and the PBCatSearch compatibility function.
**
** by Jim Luther, Apple Developer Technical Support Emeritus
**
** File: Search.c
**
** Copyright © 1992-1996 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.
*/
#include <Types.h>
#include <Gestalt.h>
#include <Timer.h>
#include <Errors.h>
#include <Memory.h>
#include <Files.h>
#include <TextUtils.h>
#define __COMPILINGMOREFILES
#include "MoreFiles.h"
#include "MoreFilesExtras.h"
#include "MoreFilesSearch.h"
/*****************************************************************************/
enum
{
/* Number of LevelRecs to add each time the searchStack is grown */
/* 20 levels is probably more than reasonable for most volumes. */
/* If more are needed, they are allocated 20 levels at a time. */
kAdditionalLevelRecs = 20
};
/*****************************************************************************/
/*
** LevelRecs are used to store the directory ID and index whenever
** IndexedSearch needs to either scan a sub-directory, or return control
** to the caller because the call has timed out or the number of
** matches requested has been found. LevelRecs are stored in an array
** used as a stack.
*/
struct LevelRec
{
long dirModDate; /* for detecting most (but not all) catalog changes */
long dirID;
short index;
};
typedef struct LevelRec LevelRec;
typedef LevelRec *LevelRecPtr, **LevelRecHandle;
/*
** SearchPositionRec is my version of a CatPositionRec. It holds the
** information I need to resuming searching.
*/
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=mac68k
#endif
struct SearchPositionRec
{
long initialize; /* Goofy checksum of volume information used to make */
/* sure we're resuming a search on the same volume. */
unsigned short stackDepth; /* Current depth on searchStack. */
short priv[11]; /* For future use... */
};
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=reset
#endif
typedef struct SearchPositionRec SearchPositionRec;
typedef SearchPositionRec *SearchPositionRecPtr;
/*
** ExtendedTMTask is a TMTask record extended to hold the timer flag.
*/
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=mac68k
#endif
struct ExtendedTMTask
{
TMTask theTask;
Boolean stopSearch; /* the Time Mgr task will set stopSearch to */
/* true when the timer expires */
};
#if PRAGMA_ALIGN_SUPPORTED
#pragma options align=reset
#endif
typedef struct ExtendedTMTask ExtendedTMTask;
typedef ExtendedTMTask *ExtendedTMTaskPtr;
/*****************************************************************************/
static OSErr CheckVol(StringPtr pathname,
short vRefNum,
short *realVRefNum,
long *volID);
static OSErr CheckStack(unsigned short stackDepth,
LevelRecHandle searchStack,
Size *searchStackSize);
static OSErr VerifyUserPB(CSParamPtr userPB,
Boolean *includeFiles,
Boolean *includeDirs,
Boolean *includeNames);
static Boolean IsSubString(StringPtr aStringPtr,
StringPtr subStringPtr);
static Boolean CompareMasked(const long *data1,
const long *data2,
const long *mask,
short longsToCompare);
static void CheckForMatches(CInfoPBPtr cPB,
CSParamPtr userPB,
const Str63 matchName,
Boolean includeFiles,
Boolean includeDirs);
#ifdef __WANTPASCALELIMINATION
#undef pascal
#endif
#if GENERATINGCFM
static pascal void TimeOutTask(TMTaskPtr tmTaskPtr);
#else
static pascal TMTaskPtr GetTMTaskPtr(void);
static void TimeOutTask(void);
#endif
#ifdef __WANTPASCALELIMINATION
#define pascal
#endif
static long GetDirModDate(short vRefNum,
long dirID);
/*****************************************************************************/
/*
** CheckVol gets the volume's real vRefNum and builds a volID. The volID
** is used to help insure that calls to resume searching with IndexedSearch
** are to the same volume as the last call to IndexedSearch.
*/
static OSErr CheckVol(StringPtr pathname,
short vRefNum,
short *realVRefNum,
long *volID)
{
HParamBlockRec pb;
OSErr error;
error = GetVolumeInfoNoName(pathname, vRefNum, &pb);
if ( error == noErr )
{
/* Return the real vRefNum */
*realVRefNum = pb.volumeParam.ioVRefNum;
/* Add together a bunch of things that aren't supposed to change on */
/* a mounted volume that's being searched and that should come up with */
/* a fairly unique number */
*volID = pb.volumeParam.ioVCrDate +
pb.volumeParam.ioVRefNum +
pb.volumeParam.ioVNmAlBlks +
pb.volumeParam.ioVAlBlkSiz +
pb.volumeParam.ioVFSID;
}
return ( error );
}
/*****************************************************************************/
/*
** CheckStack checks the size of the search stack (array) to see if there's
** room to push another LevelRec. If not, CheckStack grows the stack by
** another kAdditionalLevelRecs elements.
*/
static OSErr CheckStack(unsigned short stackDepth,
LevelRecHandle searchStack,
Size *searchStackSize)
{
OSErr result;
if ( (*searchStackSize / sizeof(LevelRec)) == (stackDepth + 1) )
{
/* Time to grow stack */
SetHandleSize((Handle)searchStack, *searchStackSize + (kAdditionalLevelRecs * sizeof(LevelRec)));
result = MemError(); /* should be noErr */
*searchStackSize = GetHandleSize((Handle)searchStack);
}
else
{
result = noErr;
}
return ( result );
}
/*****************************************************************************/
/*
** VerifyUserPB makes sure the parameter block passed to IndexedSearch has
** valid parameters. By making this check once, we don't have to worry about
** things like NULL pointers, strings being too long, etc.
** VerifyUserPB also determines if the search includes files and/or
** directories, and determines if a full or partial name search was requested.
*/
static OSErr VerifyUserPB(CSParamPtr userPB,
Boolean *includeFiles,
Boolean *includeDirs,
Boolean *includeNames)
{
CInfoPBPtr searchInfo1;
CInfoPBPtr searchInfo2;
searchInfo1 = userPB->ioSearchInfo1;
searchInfo2 = userPB->ioSearchInfo2;
/* ioMatchPtr cannot be NULL */
if ( userPB->ioMatchPtr == NULL )
goto ParamErrExit;
/* ioSearchInfo1 cannot be NULL */
if ( searchInfo1 == NULL )
goto ParamErrExit;
/* If any bits except partialName, fullName, or negate are set, then */
/* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required */
if ( ((userPB->ioSearchBits & ~(fsSBPartialName | fsSBFullName | fsSBNegate)) != 0) &&
( searchInfo2 == NULL ))
goto ParamErrExit;
*includeFiles = false;
*includeDirs = false;
*includeNames = false;
if ( (userPB->ioSearchBits & (fsSBPartialName | fsSBFullName)) != 0 )
{
/* If any kind of name matching is requested, then ioNamePtr in */
/* ioSearchInfo1 cannot be NULL or a zero-length string */
if ( (searchInfo1->hFileInfo.ioNamePtr == NULL) ||
(searchInfo1->hFileInfo.ioNamePtr[0] == 0) ||
(searchInfo1->hFileInfo.ioNamePtr[0] > (sizeof(Str63) - 1)) )
goto ParamErrExit;
*includeNames = true;
}
if ( (userPB->ioSearchBits & fsSBFlAttrib) != 0 )
{
/* The only attributes you can search on are the directory flag */
/* and the locked flag. */
if ( (searchInfo2->hFileInfo.ioFlAttrib & ~(ioDirMask | 0x01)) != 0 )
goto ParamErrExit;
/* interested in the directory bit? */
if ( (searchInfo2->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
{
/* yes, so do they want just directories or just files? */
if ( (searchInfo1->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
*includeDirs = true;
else
*includeFiles = true;
}
else
{
/* no interest in directory bit - get both files and directories */
*includeDirs = true;
*includeFiles = true;
}
}
else
{
/* no attribute checking - get both files and directories */
*includeDirs = true;
*includeFiles = true;
}
/* If directories are included in the search, */
/* then the locked attribute cannot be requested. */
if ( *includeDirs &&
((userPB->ioSearchBits & fsSBFlAttrib) != 0) &&
((searchInfo2->hFileInfo.ioFlAttrib & 0x01) != 0) )
goto ParamErrExit;
/* If files are included in the search, then there cannot be */
/* a search on the number of files. */
if ( *includeFiles &&
((userPB->ioSearchBits & fsSBDrNmFls) != 0) )
goto ParamErrExit;
/* If directories are included in the search, then there cannot */
/* be a search on file lengths. */
if ( *includeDirs &&
((userPB->ioSearchBits & (fsSBFlLgLen | fsSBFlPyLen | fsSBFlRLgLen | fsSBFlRPyLen)) != 0) )
goto ParamErrExit;
return ( noErr );
ParamErrExit:
return ( paramErr );
}
/*****************************************************************************/
/*
** IsSubString checks to see if a string is a substring of another string.
** Both input strings have already been converted to all uppercase using
** UprString (the same non-international call the File Manager uses).
*/
static Boolean IsSubString(StringPtr aStringPtr,
StringPtr subStringPtr)
{
short strLength; /* length of string */
short subStrLength; /* length of subString */
Boolean found; /* result of test */
short index; /* current index into string */
found = false;
strLength = aStringPtr[0];
subStrLength = subStringPtr[0];
if ( subStrLength <= strLength)
{
register short count; /* search counter */
register short strIndex; /* running index into string */
register short subStrIndex; /* running index into subString */
/* start looking at first character */
index = 1;
/* continue looking until remaining string is shorter than substring */
count = strLength - subStrLength + 1;
do
{
strIndex = index; /* start string index at index */
subStrIndex = 1; /* start subString index at 1 */
while ( !found && (aStringPtr[strIndex] == subStringPtr[subStrIndex]) )
{
if ( subStrIndex == subStrLength )
{
/* all characters in subString were found */
found = true;
}
else
{
/* check next character of substring against next character of string */
++subStrIndex;
++strIndex;
}
}
if ( !found )
{
/* start substring search again at next string character */
++index;
--count;
}
} while ( count != 0 && (!found) );
}
return ( found );
}
/*****************************************************************************/
/*
** CompareMasked does a bitwise comparison with mask on 1 or more longs.
** data1 and data2 are first exclusive-ORed together resulting with bits set
** where they are different. That value is then ANDed with the mask resulting
** with bits set if the test fails. true is returned if the tests pass.
*/
static Boolean CompareMasked(const long *data1,
const long *data2,
const long *mask,
short longsToCompare)
{
Boolean result = true;
while ( (longsToCompare != 0) && (result == true) )
{
/* (*data1 ^ *data2) = bits that are different, so... */
/* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */
if ( ((*data1 ^ *data2) & *mask) != 0 )
result = false;
++data1;
++data2;
++mask;
--longsToCompare;
}
return ( result );
}
/*****************************************************************************/
/*
** Check for matches compares the search criteria in userPB to the file
** system object in cPB. If there's a match, then the information in cPB is
** is added to the match array and the actual match count is incremented.
*/
static void CheckForMatches(CInfoPBPtr cPB,
CSParamPtr userPB,
const Str63 matchName,
Boolean includeFiles,
Boolean includeDirs)
{
long searchBits;
CInfoPBPtr searchInfo1;
CInfoPBPtr searchInfo2;
Str63 itemName; /* copy of object's name for partial name matching */
Boolean foundMatch;
foundMatch = false; /* default to no match */
searchBits = userPB->ioSearchBits;
searchInfo1 = userPB->ioSearchInfo1;
searchInfo2 = userPB->ioSearchInfo2;
/* Into the if statements that go on forever... */
if ( (cPB->hFileInfo.ioFlAttrib & ioDirMask) == 0 )
{
if (!includeFiles)
goto Failed;
}
else
{
if (!includeDirs)
goto Failed;
}
if ( (searchBits & fsSBPartialName) != 0 )
{
if ( (cPB->hFileInfo.ioNamePtr[0] > 0) &&
(cPB->hFileInfo.ioNamePtr[0] <= (sizeof(Str63) - 1)) )
{
/* Make uppercase copy of object name */
BlockMoveData(cPB->hFileInfo.ioNamePtr,
itemName,
cPB->hFileInfo.ioNamePtr[0] + 1);
/* Use the same non-international call the File Manager uses */
UpperString(itemName, true);
}
else
{
goto Failed;
}
{
if ( !IsSubString(itemName, (StringPtr)matchName) )
{
goto Failed;
}
else if ( searchBits == fsSBPartialName )
{
/* optimize for name matching only since it is most common way to search */
goto Hit;
}
}
}
if ( (searchBits & fsSBFullName) != 0 )
{
/* Use the same non-international call the File Manager uses */
if ( !EqualString(cPB->hFileInfo.ioNamePtr, matchName, false, true) )
{
goto Failed;
}
else if ( searchBits == fsSBFullName )
{
/* optimize for name matching only since it is most common way to search */
goto Hit;
}
}
if ( (searchBits & fsSBFlParID) != 0 )
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlParID) < (unsigned long)(searchInfo1->hFileInfo.ioFlParID)) ||
((unsigned long)(cPB->hFileInfo.ioFlParID) > (unsigned long)(searchInfo2->hFileInfo.ioFlParID)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlAttrib) != 0 )
{
if ( ((cPB->hFileInfo.ioFlAttrib ^ searchInfo1->hFileInfo.ioFlAttrib) &
searchInfo2->hFileInfo.ioFlAttrib) != 0 )
{
goto Failed;
}
}
if ( (searchBits & fsSBDrNmFls) != 0 )
{
if ( ((unsigned long)(cPB->dirInfo.ioDrNmFls) < (unsigned long)(searchInfo1->dirInfo.ioDrNmFls)) ||
((unsigned long)(cPB->dirInfo.ioDrNmFls) > (unsigned long)(searchInfo2->dirInfo.ioDrNmFls)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlFndrInfo) != 0 ) /* fsSBFlFndrInfo is same as fsSBDrUsrWds */
{
if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlFndrInfo),
(long *)&(searchInfo1->hFileInfo.ioFlFndrInfo),
(long *)&(searchInfo2->hFileInfo.ioFlFndrInfo),
sizeof(FInfo) / sizeof(long)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlXFndrInfo) != 0 ) /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */
{
if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlXFndrInfo),
(long *)&(searchInfo1->hFileInfo.ioFlXFndrInfo),
(long *)&(searchInfo2->hFileInfo.ioFlXFndrInfo),
sizeof(FXInfo) / sizeof(long)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlLgLen) != 0 )
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlLgLen)) ||
((unsigned long)(cPB->hFileInfo.ioFlLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlLgLen)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlPyLen) != 0 )
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlPyLen)) ||
((unsigned long)(cPB->hFileInfo.ioFlPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlPyLen)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlRLgLen) != 0 )
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRLgLen)) ||
((unsigned long)(cPB->hFileInfo.ioFlRLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRLgLen)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlRPyLen) != 0 )
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRPyLen)) ||
((unsigned long)(cPB->hFileInfo.ioFlRPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRPyLen)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlCrDat) != 0 ) /* fsSBFlCrDat is same as fsSBDrCrDat */
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlCrDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlCrDat)) ||
((unsigned long)(cPB->hFileInfo.ioFlCrDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlCrDat)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlMdDat) != 0 ) /* fsSBFlMdDat is same as fsSBDrMdDat */
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlMdDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlMdDat)) ||
((unsigned long)(cPB->hFileInfo.ioFlMdDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlMdDat)) )
{
goto Failed;
}
}
if ( (searchBits & fsSBFlBkDat) != 0 ) /* fsSBFlBkDat is same as fsSBDrBkDat */
{
if ( ((unsigned long)(cPB->hFileInfo.ioFlBkDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlBkDat)) ||
((unsigned long)(cPB->hFileInfo.ioFlBkDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlBkDat)) )
{
goto Failed;
}
}
/* Hey, we passed all of the tests! */
Hit:
foundMatch = true;
/* foundMatch is false if code jumps to Failed */
Failed:
/* Do we reverse our findings? */
if ( (searchBits & fsSBNegate) != 0 )
foundMatch = !foundMatch; /* matches are not, not matches are */
if ( foundMatch )
{
/* Move the match into the match buffer */
userPB->ioMatchPtr[userPB->ioActMatchCount].vRefNum = cPB->hFileInfo.ioVRefNum;
userPB->ioMatchPtr[userPB->ioActMatchCount].parID = cPB->hFileInfo.ioFlParID;
if ( cPB->hFileInfo.ioNamePtr[0] > 63 )
cPB->hFileInfo.ioNamePtr[0] = 63;
BlockMoveData(cPB->hFileInfo.ioNamePtr,
userPB->ioMatchPtr[userPB->ioActMatchCount].name,
cPB->hFileInfo.ioNamePtr[0] + 1);
/* increment the actual count */
++(userPB->ioActMatchCount);
}
}
/*****************************************************************************/
/*
** TimeOutTask is executed when the timer goes off. It simply sets the
** stopSearch field to true. After each object is found and possibly added
** to the matches buffer, stopSearch is checked to see if the search should
** continue.
*/
#ifdef __WANTPASCALELIMINATION
#undef pascal
#endif
#if GENERATINGCFM
static pascal void TimeOutTask(TMTaskPtr tmTaskPtr)
{
((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true;
}
#else
static pascal TMTaskPtr GetTMTaskPtr(void)
ONEWORDINLINE(0x2e89); /* MOVE.L A1,(SP) */
static void TimeOutTask(void)
{
((ExtendedTMTaskPtr)GetTMTaskPtr())->stopSearch = true;
}
#endif
#ifdef __WANTPASCALELIMINATION
#define pascal
#endif
/*****************************************************************************/
/*
** GetDirModDate returns the modification date of a directory. If there is
** an error getting the modification date, -1 is returned to indicate
** something went wrong.
*/
static long GetDirModDate(short vRefNum,
long dirID)
{
CInfoPBRec pb;
Str31 tempName;
long modDate;
/* Protection against File Sharing problem */
tempName[0] = 0;
pb.dirInfo.ioNamePtr = tempName;
pb.dirInfo.ioVRefNum = vRefNum;
pb.dirInfo.ioDrDirID = dirID;
pb.dirInfo.ioFDirIndex = -1; /* use ioDrDirID */
if ( PBGetCatInfoSync(&pb) == noErr )
{
modDate = pb.dirInfo.ioDrMdDat;
}
else
{
modDate = -1;
}
return ( modDate );
}
/*****************************************************************************/
pascal OSErr IndexedSearch(CSParamPtr pb,
long dirID)
{
static LevelRecHandle searchStack = NULL; /* static handle to LevelRec stack */
static Size searchStackSize = 0; /* size of static handle */
SearchPositionRecPtr catPosition;
long modDate;
short index;
ExtendedTMTask timerTask;
OSErr result;
short realVRefNum;
Str63 itemName;
CInfoPBRec cPB;
long tempLong;
Boolean includeFiles;
Boolean includeDirs;
Boolean includeNames;
Str63 upperName;
timerTask.stopSearch = false; /* don't stop yet! */
/* If request has a timeout, install a Time Manager task. */
if ( pb->ioSearchTime != 0 )
{
/* Start timer */
timerTask.theTask.tmAddr = NewTimerProc(TimeOutTask);
InsTime((QElemPtr)&(timerTask.theTask));
PrimeTime((QElemPtr)&(timerTask.theTask), pb->ioSearchTime);
}
/* Check the parameter block passed for things that we don't want to assume */
/* are OK later in the code. For example, make sure pointers to data structures */
/* and buffers are not NULL. And while we're in there, see if the request */
/* specified searching for files, directories, or both, and see if the search */
/* was by full or partial name. */
result = VerifyUserPB(pb, &includeFiles, &includeDirs, &includeNames);
if ( result == noErr )
{
pb->ioActMatchCount = 0; /* no matches yet */
if ( includeNames )
{
/* The search includes seach by full or partial name. */
/* Make an upper case copy of the match string to pass to */
/* CheckForMatches. */
BlockMoveData(pb->ioSearchInfo1->hFileInfo.ioNamePtr,
upperName,
pb->ioSearchInfo1->hFileInfo.ioNamePtr[0] + 1);
/* Use the same non-international call the File Manager uses */
UpperString(upperName, true);
}
/* Prevent casting to my type throughout code */
catPosition = (SearchPositionRecPtr)&pb->ioCatPosition;
/* Create searchStack first time called */
if ( searchStack == NULL )
{
searchStack = (LevelRecHandle)NewHandle(kAdditionalLevelRecs * sizeof(LevelRec));
}
/* Make sure searchStack really exists */
if ( searchStack != NULL )
{
searchStackSize = GetHandleSize((Handle)searchStack);
/* See if the search is a new search or a resumed search. */
if ( catPosition->initialize == 0 )
{
/* New search. */
/* Get the real vRefNum and fill in catPosition->initialize. */
result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &catPosition->initialize);
if ( result == noErr )
{
/* clear searchStack */
catPosition->stackDepth = 0;
/* use dirID parameter passed and... */
index = -1; /* start with the passed directory itself! */
}
}
else
{
/* We're resuming a search. */
/* Get the real vRefNum and make sure catPosition->initialize is valid. */
result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &tempLong);
if ( result == noErr )
{
/* Make sure the resumed search is to the same volume! */
if ( catPosition->initialize == tempLong )
{
/* For resume, catPosition->stackDepth > 0 */
if ( catPosition->stackDepth > 0 )
{
/* Position catPosition->stackDepth to access last saved level */
--(catPosition->stackDepth);
/* Get the dirID and index for the next item */
dirID = (*searchStack)[catPosition->stackDepth].dirID;
index = (*searchStack)[catPosition->stackDepth].index;
/* Check the dir's mod date against the saved mode date on our "stack" */
modDate = GetDirModDate(realVRefNum, dirID);
if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
result = catChangedErr;
}
else
{
/* Invalid catPosition record was passed */
result = paramErr;
}
}
else
{
/* The volume is not the same */
result = catChangedErr;
}
}
}
if ( result == noErr )
{
/* ioNamePtr and ioVRefNum only need to be set up once. */
cPB.hFileInfo.ioNamePtr = itemName;
cPB.hFileInfo.ioVRefNum = realVRefNum;
/*
** Here's the loop that:
** Finds the next item on the volume.
** If noErr, calls the code to check for matches and add matches
** to the match buffer.
** Sets up dirID and index for to find the next item on the volume.
**
** The looping ends when:
** (a) an unexpected error is returned by PBGetCatInfo. All that
** is expected is noErr and fnfErr (after the last item in a
** directory is found).
** (b) the caller specified a timeout and our Time Manager task
** has fired.
** (c) the number of matches requested by the caller has been found.
** (d) the last item on the volume was found.
*/
do
{
/* get the next item */
cPB.hFileInfo.ioFDirIndex = index;
cPB.hFileInfo.ioDirID = dirID;
result = PBGetCatInfoSync(&cPB);
if ( index != -1 )
{
if ( result == noErr )
{
/* We found something */
CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
++index;
if ( (cPB.dirInfo.ioFlAttrib & ioDirMask) != 0 )
{
/* It's a directory */
result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
if ( result == noErr )
{
/* Save the current state on the searchStack */
/* when we come back, this is where we'll start */
(*searchStack)[catPosition->stackDepth].dirID = dirID;
(*searchStack)[catPosition->stackDepth].index = index;
(*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
/* position catPosition->stackDepth for next saved level */
++(catPosition->stackDepth);
/* The next item to get is the 1st item in the child directory */
dirID = cPB.dirInfo.ioDrDirID;
index = 1;
}
}
/* else do nothing for files */
}
else
{
/* End of directory found (or we had some error and that */
/* means we have to drop out of this directory). */
/* Restore last thing put on stack and */
/* see if we need to continue or quit. */
if ( catPosition->stackDepth > 0 )
{
/* position catPosition->stackDepth to access last saved level */
--(catPosition->stackDepth);
dirID = (*searchStack)[catPosition->stackDepth].dirID;
index = (*searchStack)[catPosition->stackDepth].index;
/* Check the dir's mod date against the saved mode date on our "stack" */
modDate = GetDirModDate(realVRefNum, dirID);
if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
{
result = catChangedErr;
}
else
{
/* Going back to ancestor directory. */
/* Clear error so we can continue. */
result = noErr;
}
}
else
{
/* We hit the bottom of the stack, so we'll let the */
/* the eofErr drop us out of the loop. */
result = eofErr;
}
}
}
else
{
/* Special case for index == -1; that means that we're starting */
/* a new search and so the first item to check is the directory */
/* passed to us. */
if ( result == noErr )
{
/* We found something */
CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
/* Now, set the index to 1 and then we're ready to look inside */
/* the passed directory. */
index = 1;
}
}
} while ( (!timerTask.stopSearch) && /* timer hasn't fired */
(result == noErr) && /* no unexpected errors */
(pb->ioReqMatchCount > pb->ioActMatchCount) ); /* we haven't found our limit */
/* Did we drop out of the loop because of timeout or */
/* ioReqMatchCount was found? */
if ( result == noErr )
{
result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
if ( result == noErr )
{
/* Either there was a timeout or ioReqMatchCount was reached. */
/* Save the dirID and index for the next time we're called. */
(*searchStack)[catPosition->stackDepth].dirID = dirID;
(*searchStack)[catPosition->stackDepth].index = index;
(*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
/* position catPosition->stackDepth for next saved level */
++(catPosition->stackDepth);
}
}
}
}
else
{
/* searchStack Handle could not be allocated */
result = memFullErr;
}
}
if ( pb->ioSearchTime != 0 )
{
/* Stop Time Manager task here if it was installed */
RmvTime((QElemPtr)&(timerTask.theTask));
DisposeRoutineDescriptor(timerTask.theTask.tmAddr);
}
return ( result );
}
/*****************************************************************************/
pascal OSErr PBCatSearchSyncCompat(CSParamPtr paramBlock)
{
static Boolean fullExtFSDispatchingtested = false;
static Boolean hasFullExtFSDispatching = false;
OSErr result;
Boolean supportsCatSearch;
long response;
GetVolParmsInfoBuffer volParmsInfo;
long infoSize;
result = noErr;
/* See if File Manager will pass CatSearch requests to external file systems */
/* we'll store the results in a static variable so we don't have to call Gestalt */
/* everytime we're called. */
if ( !fullExtFSDispatchingtested )
{
fullExtFSDispatchingtested = true;
if ( Gestalt(gestaltFSAttr, &response) == noErr )
{
hasFullExtFSDispatching = ((response & (1L << gestaltFullExtFSDispatching)) != 0);
}
}
/* CatSearch is a per volume attribute, so we have to check each time we're */
/* called to see if it is available on the volume specified. */
supportsCatSearch = false;
if ( hasFullExtFSDispatching )
{
infoSize = sizeof(GetVolParmsInfoBuffer);
result = HGetVolParms(paramBlock->ioNamePtr, paramBlock->ioVRefNum,
&volParmsInfo, &infoSize);
if ( result == noErr )
{
supportsCatSearch = hasCatSearch(volParmsInfo);
}
}
/* noErr or paramErr is OK here. */
/* paramErr just means that GetVolParms isn't supported by this volume */
if ( (result == noErr) || (result == paramErr) )
{
if ( supportsCatSearch )
{
/* Volume supports CatSearch so use it. */
/* CatSearch is faster than an indexed search. */
result = PBCatSearchSync(paramBlock);
}
else
{
/* Volume doesn't support CatSearch so */
/* search using IndexedSearch from root directory. */
result = IndexedSearch(paramBlock, fsRtDirID);
}
}
return ( result );
}
/*****************************************************************************/
pascal OSErr NameFileSearch(StringPtr volName,
short vRefNum,
ConstStr255Param fileName,
FSSpecPtr matches,
long reqMatchCount,
long *actMatchCount,
Boolean newSearch,
Boolean partial)
{
CInfoPBRec searchInfo1, searchInfo2;
HParamBlockRec pb;
OSErr error;
static CatPositionRec catPosition;
static short lastVRefNum = 0;
/* get the real volume reference number */
error = DetermineVRefNum(volName, vRefNum, &vRefNum);
if ( error != noErr )
return ( error );
pb.csParam.ioNamePtr = NULL;
pb.csParam.ioVRefNum = vRefNum;
pb.csParam.ioMatchPtr = matches;
pb.csParam.ioReqMatchCount = reqMatchCount;
pb.csParam.ioSearchBits = ( partial ) ? /* tell CatSearch what we're looking for: */
( fsSBPartialName + fsSBFlAttrib ) : /* partial name file matches or */
( fsSBFullName + fsSBFlAttrib ); /* full name file matches */
pb.csParam.ioSearchInfo1 = &searchInfo1;
pb.csParam.ioSearchInfo2 = &searchInfo2;
pb.csParam.ioSearchTime = 0;
if ( (newSearch) || /* If caller specified new search */
(lastVRefNum != vRefNum) ) /* or if last search was to another volume, */
{
catPosition.initialize = 0; /* then search from beginning of catalog */
}
pb.csParam.ioCatPosition = catPosition;
pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);
/* search for fileName */
searchInfo1.hFileInfo.ioNamePtr = (StringPtr)fileName;
searchInfo2.hFileInfo.ioNamePtr = NULL;
/* only match files (not directories) */
searchInfo1.hFileInfo.ioFlAttrib = 0x00;
searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;
error = PBCatSearchSyncCompat((CSParamPtr)&pb);
if ( (error == noErr) || /* If no errors or the end of catalog was */
(error == eofErr) ) /* found, then the call was successful so */
{
*actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */
}
else
{
*actMatchCount = 0; /* else no matches found */
}
if ( (error == noErr) || /* If no errors */
(error == catChangedErr) ) /* or there was a change in the catalog */
{
catPosition = pb.csParam.ioCatPosition;
lastVRefNum = vRefNum;
/* we can probably start the next search where we stopped this time */
}
else
{
catPosition.initialize = 0;
/* start the next search from beginning of catalog */
}
if ( pb.csParam.ioOptBuffer != NULL )
{
DisposePtr(pb.csParam.ioOptBuffer);
}
return ( error );
}
/*****************************************************************************/
pascal OSErr CreatorTypeFileSearch(StringPtr volName,
short vRefNum,
OSType creator,
OSType fileType,
FSSpecPtr matches,
long reqMatchCount,
long *actMatchCount,
Boolean newSearch)
{
CInfoPBRec searchInfo1, searchInfo2;
HParamBlockRec pb;
OSErr error;
static CatPositionRec catPosition;
static short lastVRefNum = 0;
/* get the real volume reference number */
error = DetermineVRefNum(volName, vRefNum, &vRefNum);
if ( error != noErr )
return ( error );
pb.csParam.ioNamePtr = NULL;
pb.csParam.ioVRefNum = vRefNum;
pb.csParam.ioMatchPtr = matches;
pb.csParam.ioReqMatchCount = reqMatchCount;
pb.csParam.ioSearchBits = fsSBFlAttrib + fsSBFlFndrInfo; /* Looking for finder info file matches */
pb.csParam.ioSearchInfo1 = &searchInfo1;
pb.csParam.ioSearchInfo2 = &searchInfo2;
pb.csParam.ioSearchTime = 0;
if ( (newSearch) || /* If caller specified new search */
(lastVRefNum != vRefNum) ) /* or if last search was to another volume, */
{
catPosition.initialize = 0; /* then search from beginning of catalog */
}
pb.csParam.ioCatPosition = catPosition;
pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);
/* no fileName */
searchInfo1.hFileInfo.ioNamePtr = NULL;
searchInfo2.hFileInfo.ioNamePtr = NULL;
/* only match files (not directories) */
searchInfo1.hFileInfo.ioFlAttrib = 0x00;
searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;
/* search for creator; if creator = 0x00000000, ignore creator */
searchInfo1.hFileInfo.ioFlFndrInfo.fdCreator = creator;
searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = ( creator == (OSType)0x00000000 ) ?
(OSType)0x00000000 :
(OSType)0xffffffff;
/* search for fileType; if fileType = 0x00000000, ignore fileType */
searchInfo1.hFileInfo.ioFlFndrInfo.fdType = fileType;
searchInfo2.hFileInfo.ioFlFndrInfo.fdType = ( fileType == (OSType)0x00000000 ) ?
(OSType)0x00000000 :
(OSType)0xffffffff;
/* zero all other FInfo fields */
searchInfo1.hFileInfo.ioFlFndrInfo.fdFlags = 0;
searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
searchInfo1.hFileInfo.ioFlFndrInfo.fdFldr = 0;
searchInfo2.hFileInfo.ioFlFndrInfo.fdFlags = 0;
searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
searchInfo2.hFileInfo.ioFlFndrInfo.fdFldr = 0;
error = PBCatSearchSyncCompat((CSParamPtr)&pb);
if ( (error == noErr) || /* If no errors or the end of catalog was */
(error == eofErr) ) /* found, then the call was successful so */
{
*actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */
}
else
{
*actMatchCount = 0; /* else no matches found */
}
if ( (error == noErr) || /* If no errors */
(error == catChangedErr) ) /* or there was a change in the catalog */
{
catPosition = pb.csParam.ioCatPosition;
lastVRefNum = vRefNum;
/* we can probably start the next search where we stopped this time */
}
else
{
catPosition.initialize = 0;
/* start the next search from beginning of catalog */
}
if ( pb.csParam.ioOptBuffer != NULL )
{
DisposePtr(pb.csParam.ioOptBuffer);
}
return ( error );
}
/*****************************************************************************/