mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
1231 lines
36 KiB
C
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 );
|
|
}
|
|
|
|
/*****************************************************************************/
|