Standard Search Unit
Volume Number: 7
Issue Number: 10
Column Tag: Pascal Forum
By Ed Eichman, Mak Jukie, B & E Software, Germany
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
[Ed & Mak work as programmers for B & E Software, makers of the program
RagTime 3. Ed is a former musician who was able to find another job that would let him
sleep till noon every day, and Mak will not admit to having done anything prior to
writing this article]
The Concept
While working on the latest release of RagTime 3, we realized that we needed a
better way to organize the extra files that ship with the program. RagTime is an
integrated page layout/spreadsheet/text processing/graphing/picture program which
supports text import and direct scanning into the program via external code resources.
The combination of scanner drivers, text filters, one or more dictionaries, PPD files,
etc. would make for a large mess inside the users system folder, so we decided to use a
standard folder into which all auxiliary files could be kept, and shared with other
programs if desired.
This led to the development of a standard search unit (SSU) for our files. The
SSU looks for a folder (for the remainder of this article, we will refer to this folder as
the ‘Company Folder’) in which all the files which are needed by the program are kept.
This folder will first be searched for in the folder from which the application was
launched, and then in the current system folder. The first folder found with the
specified name will then become the target folder. The SSU will not only access the files
within the ‘Company Folder’, but also recursively check all folders within the
‘Company Folder’ hierarchy. This will allow the user to organize the target folder in
any way that is convenient.
This folder could have a number of uses. If you are writing a number of
applications that all need to use a dictionary, for example, you could place one
dictionary into the ‘Company Folder’ in the system folder, and have all the applications
access this folder. You could also define a interface for external code resources, and just
pop them into the ‘Company Folder’ to access them while the program is running.
Finally, the SSU can keep track of multiple resources in one or more files. This could
be handy if, for instance, you have a game which uses a number of large sound
resources; these resources could be put in one file and only used by the program if the
user chooses to put them on his or her hard disk in the appropriate folder.
Set Up
In order to use the SSU, you must first call InitSSU to set up three global
variables. The first, gCompanyFStruct, is initialized to NIL and will eventually contain
the hierarchy within the ‘Company Folder’. Next, gAppWDRefNum and gSysWDRefNum
will be initialized to contain the Working Directory Reference Number of the folder
containing the current application and the current System Folder, respectively.
The List
The SSU stores information about specified files or resources contained in the
company folder in a linked list. This list contains records of type ItemInfoRec which
contains the following information:
il_nextItemInfo is of type ItemInfoHdl and contains a handle to the next record in
the linked list. When this field is NIL, it indicates that you are at the end of the list.
il_privData is provided to the caller of the SSU to store any extra data. The
caller may use this field in any way; usually to point to a block of memory that holds
information about the item in the list. The best time to enter data in this field is during
a call to your CallIdentifyProc (see below).
Please read the description of the killPrivData Procedure (below) to get a better
understanding of when you can expect the il_privData field to change.
il_ resourceType contains the resource type if this item is a resource, and will
be NIL otherwise.
il_resID contains the resource number if this item is a resource, and will be NIL
otherwise.
il_fileType will contain the file type of the file in the list, or the file type of the
file which contains the specified resource.
il_fileSpec contains the vRefnum, the dirID, and the file name of either the file
in the list or the file which contains the specified resource.
il_nickName may contain an alternate name for the item. For instance, if you
request a list that contains all the sound resources from a particular file, this field will
either contain the resource name, or the type and number of the specified resource if
the resource has no name (i.e. ‘snd resource number 42’).
The SSU is capable of creating multiple lists from the ‘Company Folder’. For
instance, in RagTime 3 we keep a list of all scanner drivers and a separate list of all
text import filters which are available. Both list are created from one folder.
How a List is built
The list is created on the first call to SSU_BuildList:
{1}
PROCEDURE SSU_BuildList(VAR biiList BuildItemInfoAPT;
numTypes:Integer; VAR result:SSUInfoRec; killPrivData:ProcPtr);
This procedure should be called each time you want to use the list. The first time
SSU_BuildList is called, it puts the tick count of the last modification date of the
‘Company Folder’ into a field from gCompanyFStruct (this global is used by all list
which the SSU builds). Thereafter, each time you call SSU_BuildList and pass in the list
created on the first call to the procedure, it determines if the list must be rebuilt
(based on comparing the previous modification tick count to the current one) and
rebuilds the list if necessary.
biiList is an array of type BuildItemInfoAPT. Each member of the array contains
criteria to decide if a particular file/resource should be added to the current list. Each
search criteria must contain at least a file type. Files / resources for the list can then
be chosen by creator, resource type, file name, or any combination of the three.
Additionally, any files that match the above search criteria can be passed to a
caller supplied procedure (passed in the bi_procAddr as a ProcPtr) which can either
accept or reject the file/ resource, and/or which can be used to setup the il_privData
field. The procedure should have the following setup:
{2}
PROCEDURE CallIdentifyProc(VAR theName: Str255; theResH:Handle;
theID:Integer; VAR privData:LongInt);
theName will either be the file name or the il_nickName field from the
FileInfoRec if the item in question is a resource. This argument is guaranteed not to be
empty upon entry to this procedure. If the CallIdentifyProc wants to reject the item, an
empty string should be returned here. The name may also be changed if a different name
is desired.
If the current item is a resource, theResH will contain a handle to the resource,
and theID will contain the resID. If the current item is a file, theResH will be NIL and
theID will contain the file reference number of the file.
privData is a LongInt which will be 0 on entry, and may be used in any way by
the application. This value will be put in the il_privData field of the ItemInfoRecord.
The next argument to the SSU_BuildList procedure is numTypes, which must
contain the number of BuildItemInfoRT records that are being passed in the array.
The result argument must be cleared before calling this routine for the first
time. Thereafter, result must contain the SSUInfoRec returned on the previous call to
SSU_BuildList for the list in question. The argument result can be cleared with a call to
BlockClear from the SSU like this:
{3}
BlockClear(@result, SizeOf(result));
BlockClear can be useful in a number of other situations (such as clearing a
param block before giving it to the low level file manager calls), and the code for the
call is listed below:
{4}
PROCEDURE BlockClear(thePtr:UNIV Ptr; count:Integer);
BEGIN
WHILE count > 0 DO BEGIN
count := count - 1;
thePtr^ := 0;
thePtr := Ptr(LongInt(thePtr)+1);
END;
END;
Finally, in case the list should need to be rebuilt, your call to SSU_BuildList
should include a ProcPtr to a procedure that can dispose of any private data that you
have set up. The setup for your procedure must be:
{5}
PROCEDURE KillPrivData(VAR pData:LongInt);
Please notice that your private data could be destroyed any time that you call
SSU_BuildList. If the SSU determines the list must be rebuilt (based on the tick count,
described above), it totally destroys the old list before building the new list.
If you have not allocated any privData, this argument can be set to NIL.
Using items from a List
The list may contain both files and resources. However, the SSU only opens or
closes files from the list, or files which contain the resource in the item list. These
files may be opened with a call to the function SSU_OpenFile:
{6}
FUNCTION SSU_OpenFile(theItemInfoHdl: ItemInfoHdl; VAR
fileRefNum:Integer; permission:Byte; openResFork: Boolean):OSErr;
theItemInfoHdl must contain a handle to the item that you want to open (if the
item is a resource, all needed info to open the file is found in its ItemInfoHdl; you don’t
have to create a separate handle for a call to SSU_OpenFile). The argument fileRefNum
will be zeroed by the SSU before being used by the function, and will return the file
reference number of the opened file. The permission argument specifies the access
privileges that you are requesting for the file to be opened. The constants for this
argument may be found in Inside Mac, Volume 2, page 100.
The openResFork boolean should be set to TRUE if you want the resource fork
rather than the data fork of the file to be opened. Setting this boolean to TRUE is
required if you are trying to access an item which is a resource, but could also be set to
TRUE if you want to access resources from a file in the item list. Finally, the
SSU_OpenFile function returns an OSErr indicating the success or failure of the
function.
Each call to SSU_OpenFile should be offset by a call to SSU_CloseFile after you
are done using the file/ resource.
{7}
FUNCTION SSU_CloseFile(VAR fileRefNum: Integer;
closeResFork:Boolean):OSErr;
This routine closes the file opened by SSU_OpenFile. The argument fileRefNum
should be set to the file reference number returned in the call to SSU_OpenFile, and
closeResFork should have the same value that was originally used for SSU_OpenFile.
fileRefNum is declared a VAR variable so that the file reference number of the
file can be set to zero when the file is closed. SSU_CloseFile first checks to make sure
fileRefNum is not zero before trying to close the file, so that multiple calls to
SSU_CloseFile for the same file will not cause errors.
Disposing of a List
After you are done using a list, the memory allocated to the maintenance of the
list must be disposed of. This is accomplished with a call to SSU_DisposList:
{8}
PROCEDURE SSU_DisposList(VAR theResult: SSUInfoRec;
killPrivData:ProcPtr);
theResult is passed as a VAR parameter so that it may be set to NIL after the list
is disposed of. This way, if theResult is passed again to SSU_BuildList, the SSU will
build a new list instead of dying painfully.
The KillPrivData ProcPtr is explained above in the description of
SSU_BuildList.
It should be noted here that a list should only be disposed of when you are
completely done using it, or when you can not afford the memory that the list takes up,
since rebuilding the list can be time consuming. Depending on the amount of files in the
‘Company Folder’ and the complexity of your CallIdentifyProc, the list could take a
couple of seconds to build on each call to SSU_BuildList.
Depending on your application, it might be worth setting up a global SSUInfoRec
for each of the lists that you will be using. This way, when you call SSU_BuildList, the
list will only be rebuilt if the ‘Company Folder’ modification tick count has changed.
Possible additions
One of the first things you will notice when displaying a list from the SSU is that
the items are not sorted. If you plan to use the list for display, a sorting procedure is a
must.
Additionally, the SSU will be more than happy to add an identical name to the list
if one is found. If this occurs with your list, you may want to check which of the two
identical files/ resources is the one you really need, and eliminate the other. This would
usually be done by checking the version resource of a file to see which of the files is
newer.
Another good idea might be to add the searching, not just of the ‘Company Folder’,
but also of the System Folder and the folder from which the application was launched.
Most of the work for this addition is already done for you in the routine
SearchDirectory. Just be sure to set the recursive argument to FALSE, or you will end
up searching the ‘Company Folder’ a second time.
Finally, you may want to have mercy on your mortal users by being a little
flexible with the naming of the ‘Company Folder’. If you find something that is fairly
close (i.e. ‘Dompany Folder’), you could put up a message asking if that is the correct
folder, and warning them that you will rename the folder to the correct name.
UNIT SSU;
INTERFACE
USES
MemTypes, OSIntf, ToolIntf, Packages;
TYPE
NickNameT = Str63; {Maximum length of an
alternate name}
CanonFileSpec = RECORD
cf_vRefNum : Integer;
cf_dirID : LongInt;