There are four methods of providing info to the user or developer. Of these, aliases and log files are suitable for end users. Callbacks and Apple Events are intended for developers.
Creating an alias is a little involved. I like the example in [Little91]. Below are a few code snippets that handle potential problems.
Remember to put the alias file somewhere meaningful. You may want to create your own folder to hold the aliases. The following code checks if a named folder exists, and if not, creates it.
First, find the parent folder of our intended destination folder. Here, the parent is Apple Menu Items, so our folder should appear in the Apple Menu.
long theFolderId; CInfoPBPtr thePBPtr; FSSpec theFSSpec; OSErr theError; theError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kCreateFolder, &theFSSpec.vRefNum, &theFSSpec.parID );
Create the new directory. If it already exists, the call will fail, and we need to get the existing dir id using PBGetCatInfo().
theError = DirCreate( theFSSpec.vRefNum, theFSSpec.parID, Ō\pMy FolderĶ, &theFolderId ); if ( theError != noErr ) { theError = DoFindOneFolder( fileError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kCreateFolder, &theFSSpec.vRefNum, &theFSSpec.parID ); thePBPtr->dirInfo.ioCompletion = 0L; thePBPtr->dirInfo.ioNamePtr = Ō\pMy FolderĶ; thePBPtr->dirInfo.ioVRefNum = theFSSpec.vRefNum; thePBPtr->dirInfo.ioDrDirID = theFSSpec.parID; thePBPtr->dirInfo.ioFDirIndex = 0; theError = PBGetCatInfo( thePBPtr, true ); while ( thePBPtr->dirInfo.ioResult == 1 ) { ; }
Check that the call succeeded, and returned a valid directory id.
if ( ( thePBPtr->dirInfo.ioResult == noErr ) && ( theError == noErr ) && ( thePBPtr->dirInfo.ioFlAttrib & 0x0010 ) ) theFolderID = thePBPtr->dirInfo.ioDrDirID; }Listing 5. Locating the destination directory.
Now use that directory id when creating the alias file. The last line of code in this listing establishes our special folder as the destination for the alias.
FSSpecPtr aliasSpec; OSErr fileError; BlockMove( theFSSpec.name, aliasSpec.name, theFSSpec.name[ 0 ] + 1 ); fileError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kDontCreateFolder, &aliasSpec.vRefNum, &aliasSpec.parID ); aliasSpec.parID = theFolderID; // Now call FSpCreateResFile(), etc.Listing 6. Settng up to create the new alias.
Logging data to a file is easy, as long as youÕre appending. This next example appends to a text file. The record format looks like this:
DateTime Path to file
For example:
Wed, May 12, 1999 10:47:05 PM Devt::tmp data
Since the user may delete the log file at any time, ensure it exists before opening.
FSSpecPtr logFSSpecPtr; OSErr fileError; fileError = HCreate( ( *logFSSpecPtr ).vRefNum, ( *logFSSpecPtr ).parID, ( *logFSSpecPtr ).name, kLogFileCreatorType, kLogFileType );
Open, and move to the end of, the file.
short fileRef; fileError = FSpOpenDF( logFSSpecPtr, kSharedPermission, &fileRef ); SetFPos( fileRef, fsFromLEOF, 0 );
Using the current time as a timestamp avoids the setup associated with (yet another call to) PBGetCatInfo(). But itÕs less accurate. As long as we get called soon enough, the difference may only be a few seconds.
unsigned long theTime; GetDateTime( &theTime );
Convert the timestamp into a human-readable date. Then write it out.
Str32 theString, IUDateString( theTime, abbrevDate, theString ); dataCount = theString[ 0 ];
Move the date string into a buffer, then write the buffer to the file. Remember to initialize any pointers (not shown).
long dataCount; short i; Ptr dataPtr; // Faster than BlockMove()? for ( i = 0; i < dataCount; i++ ) dataPtr[ i ] = theString[ i + 1 ]; fileError = FSWrite( fileRef, &dataCount, dataPtr );
Adding a
Adding the time string is done the same way, but is not shown.
Next, get the volume name.
Writing the volume name to the file is done the same way as the date string. DonÕt forget to add the two colons after the name.
Next, iterate over the directories in the fileÕs path.
As long as the result refers to a directory, use the id to find the parent of this directory.
Each iteration will place the name of the directory in the dirInfo.ioNamePtr field. This is the value we want to print, followed by a colon. However, since weÕre iterating up the directory tree, we should store the names temporarily, then write them out in the correct sequence after reaching the root.
Finally, write the name of the file, followed by a
Dispose of any pointers, close the file, and flush the volume.
Now letÕs look at notification from a developerÕs perspective.
Callbacks are great from the perspective of time. But, they can be difficult to setup initially. And you need to make sure you donÕt call a routine that has moved! This will certainly cause problems if the application youÕre calling has quit.
Registering a callback requires that both sides know what to expect. For example, we can use one registration mechanism if we can distinguish between the types of activities that should be sent to a particular callback.
Similarly, the structure of the data being exchanged must be defined. Here, weÕve added a few fields to those defined by an FSSpec. This helps the caller in several ways:
The caller still has access to the various File Manager calls to get more information if necessary.
This structure defines the data sent during an actual registration. The function address is sent, and the recipient needs to create a UniversalProcPtr based on the action specified (see the previous enum definition).
The callback convention looks as follows:
Sending in a registration request might look something like this:
Locate the function handling callback registration. Using Gestalt(), you need to know the advertised signature.
Allocate and fill a structure specifying the type of activity weÕre interested in, and the address of the callback function.
On the receiving side, the corresponding registration process looks like this in the Gestalt() handler.
We distinguish between the activities in case we want to manage _Create callbacks differently. Notice that a UPP is built here. This example also assumes the PowerPC instruction set architecture.
This example uses a single global variable to hold the callback UPP. Using a list or queue would increase the potential number of clients. [Rentzsch99] addresses this issue; you should definitely check it out.
Call the callback via CallUniversalProc(). DonÕt forget to populate the data structure first.
On the receiving side, the stack may need adjusting to account for the first two params to CallUniversalProc(). (This was found to be true when interacting with a shared library.)
Upon entering the callback, increment the stack pointer by two words:
When leaving the callback, decrement the stack pointer by the same amount:
This particular callback is part of a PowerPlant project, and simply relays the incoming data to a separate method in the application:
Figure 3 shows the raw information being fed (via the callback just discussed) to a text area for diagnostic purposes. It displays the FSSpec-related fields associated with a file creation.
Figure 3. Demo app receiving info from the file system watcher.
Apple Events provide another convenient mechanism to transmit data from one component to another. This section discusses the sending of file creation information from the patch to the background app.
In order to support scripting, and also to allow for checking of required data elements, the background app contains an 'aete' resource that defines a custom Apple Event for handling file creation. This example uses an FSSpec, rather than the FWData type discussed in the section on callbacks. The FSSpec is sent as the direct parameter for this event.
Preparing and sending an Apple Event is addressed in [Little91]. This code, located in the patch, populates an event with the most recent _Create data:
The receiver of this Apple Event must be prepared to handle it. Here, the background app registers the function HandleCreate() as the recipient of file creation events. The class and id match those found in the 'aete' resource.
The function HandleCreate() will extract the data from the event, and pass it off for additional processing. For our custom event, the itemsInList value should always be one.
*dataPtr = '\t';
dataCount = 1;
fileError = FSWrite( fileRef,
&dataCount, dataPtr );
ParamBlockRec theVolume;
OSErr fileError;
theVolume.volumeParam.ioCompletion
= 0L;
theVolume.volumeParam.ioNamePtr
= ( StringPtr )&theString;
theVolume.volumeParam.ioVRefNum
= theVRefNum;
theVolume.volumeParam.ioVolIndex
= 0;
fileError = PBGetVInfo( &theVolume,
true );
CInfoPBPtr thePBPtr;
fileError = noErr;
while ( fileError == noErr ) {
thePBPtr->dirInfo.ioCompletion =
0L;
thePBPtr->dirInfo.ioNamePtr =
tempStringPtr;
thePBPtr->dirInfo.ioVRefNum =
theVRefNum;
thePBPtr->dirInfo.ioDrDirID =
tempFolderID;
thePBPtr->dirInfo.ioFDirIndex = -1;
fileError = PBGetCatInfo( thePBPtr,
true );
while ( thePBPtr->dirInfo.ioResult == 1 ) {
;
}
if ( ( thePBPtr->dirInfo.ioResult
== noErr ) &&
( fileError == noErr ) &&
( thePBPtr->dirInfo.ioFlAttrib
& 0x0010 ) )
thePBPtr->dirInfo.ioDrDirID =
thePBPtr->dirInfo.ioDrParID;
}
StringPtr fileNamePtr;
dataCount = fileNamePtr[ 0 ];
for ( i = 0; i < dataCount; i++ )
dataPtr[ i ] =
fileNamePtr[ i + 1 ];
dataPtr[ i ] = '\r';
dataCount++;
fileError = FSWrite( fileRef,
&dataCount, dataPtr );
DisposePtr( dataPtr );
FSClose( fileRef );
fileError = FlushVol( NIL,
( *logFSSpecPtr ).vRefNum );
Listing 7. Logging new file info.
Callback functions
Callback registration
enum {
kCreateFlag,
kDeleteFlag,
kCopyFlag,
kRenameFlag
};
typedef struct FWData {
short theTrapId;
short theVRefNum;
long theParID;
Str63 theString;
OSType theFileType;
} FWData, *FWDataPtr;
typedef struct FWSubscribe {
short theAction;
long *theCallbackAddr;
} FWSubscribe, *FWSubscribePtr;
ProcInfoType uppCreateFileProcInfo =
kPascalStackBased
| RESULT_SIZE( kNoByteCode )
| STACK_ROUTINE_PARAMETER( 1,
SIZE_CODE( sizeof( FWData ) ) )
;
OSErr theErr = noErr;
SelectorFunctionUPP myGestaltUPP;
FWSubscribePtr theFWSubscribePtr;
theErr = ::Gestalt( kTargetSignature,
( long * )&myGestaltUPP );
if ( theErr == noErr ) {
theFWSubscribePtr =
( FWSubscribePtr )
NewPtr( sizeof( FWSubscribe ) );
if ( theFWSubscribePtr != nil ) {
theFWSubscribePtr->theAction =
kCreateFlag;
theFWSubscribePtr->theCallbackAddr
= ( long * )&MyCreateCallback;
theErr = CallSelectorFunctionProc(
myGestaltUPP,
GESTALT_ADD_CALLBACK,
( long * )theFWSubscribePtr );
DisposePtr(
( Ptr )theFWSubscribePtr );
}
}
Listing 8. Registering a callback: requester.
theAction = ( ( FWSubscribePtr )
theResponse )->theAction;
if ( theAction == kCreateFlag )
gCallbackProcPtr =
NewRoutineDescriptor(
( ProcPtr )( ( FWSubscribePtr )
theResponse )->theCallbackAddr,
uppCreateFileProcInfo,
kPowerPCISA );
Listing 9. Registering a callback: receiver.
Callback execution
FWData theFWData;
theFWData.theVRefNum =
gParamBlockCopy->fileParam.ioVRefNum;
// etc.
CallUniversalProc( gCallbackProcPtr,
uppCreateFileProcInfo, theFWData );
Listing 10. Calling a developer-provided routine with the new file info.
asm void PrologGlue() {
addi sp, sp, 0x08
blr
}
asm void EpilogGlue() {
subi sp, sp, 0x08
blr
}
pascal void MyCreateCallback(
FWData theFWData ) {
PrologGlue();
( ( FileWatcherDemo* )
parentApp )->HandleCreate(
theFWData );
EpilogGlue();
}
Listing 11. Inside the callback.
Sending Apple Events
resource 'aete' ( 0 ) {
0x1, 0x0, english, roman, {
"File Watcher Suite",
"Specialized stuff.", 'FWAE', 1, 1,
{
// Events
"handle new file",
"A new file to record.",
'asd9', 'newf',
noReply, "No reply.",
replyOptional, singleItem,
notEnumerated,
reserved, reserved, reserved,
reserved, reserved, reserved,
reserved, reserved, reserved,
reserved, reserved, reserved,
reserved,
'****', "FSSpec for new file",
directParamRequired, singleItem,
notEnumerated,
doesntChangeState,
reserved, reserved, reserved,
reserved, reserved, reserved,
reserved, reserved, reserved,
reserved, reserved, reserved,
{}
}, {}, {}, {},
},
};
Listing 12. Resource defining a file create Apple Event.
AEAddressDesc theAddressDesc;
AppleEvent theAppleEvent;
FSSpec theSpec;
OSErr theError;
theError = FSMakeFSSpec(
gParamBlockCopy->fileParam.ioVRefNum,
gParamBlockCopy->fileParam.ioDirID,
gParamBlockCopy->fileParam.ioNamePtr,
&theSpec );
theError = AECreateAppleEvent(
'asd9', 'newf', &theAddressDesc,
kAutoGenerateReturnID,
kAnyTransactionID,
&theAppleEvent );
theError = AEPutParamPtr(
&theAppleEvent, keyDirectObject,
typeFSS, &theSpec,
sizeof( FSSpec ) );
Listing 13. Populating an Apple Event.
const OSType kTestEventClass ='asd9';
const OSType kCreateEventId = 'newf';
AEInstallEventHandler(
kTestEventClass, kCreateEventId,
(AEEventHandlerProcPtr)HandleCreate,
0, false );
}
FSSpec theFSS;
OSErr theErr;
SFTypeList theTypeList;
Size actualSize;
for ( i = 1; i <= itemsInList; i++ ) {
theErr = AEGetNthPtr( &docList, i,
typeFSS, &theKeyword, &typeCode,
( Ptr )&theFSS, sizeof( FSSpec ),
&actualSize );
theErr = DoMakeAlias( theFSS );
}
Listing 14. Handling an Apple Event.