Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Main.c
/* |
File: Main.c |
Contains: Demonstrates how to use the kqueue mechanism to be notified when the contents |
of a folder change. Efficient method for detecting when a file is added, deleted, |
or renamed. This sample creates a simple MP thread which watches a few defined |
locations for modifications, then posts the event back to the main queue to display |
the relevant kevent information to the main window. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2004 Apple Computer, Inc., All Rights Reserved |
*/ |
#include <Carbon/Carbon.h> |
#include "Main.h" |
#include <sys/event.h> |
#include <sys/stat.h> |
#include <sys/fcntl.h> |
#include <unistd.h> |
static OSErr InitializeApplication( void ); |
static void DisplaySimpleWindow( void ); |
static pascal OSStatus AppEventEventHandlerProc( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ); |
static pascal OSStatus MPWindowEventHandlerProc( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ); |
static void PrintKEvent( WindowRef window, struct kevent *kevp ); |
static OSStatus MyMPTask( void *parameter ); |
static OSStatus PostKQueueEvent( struct kevent *kevp ); |
GlobalAppInfo g; // globals |
int main( void ) |
{ |
OSErr err; |
err = InitializeApplication(); |
if ( err != noErr ) goto Bail; |
SendCommandProcessEvent( kHICommandNew ); // Send a kHICommandNew to ourselves to create a default new window |
RunApplicationEventLoop(); |
Bail: |
if ( g.mainNib != NULL ) DisposeNibReference( g.mainNib ); |
if ( g.mainBundle != NULL ) CFRelease( g.mainBundle ); |
return( noErr ); |
} |
static OSErr InitializeApplication( void ) |
{ |
OSErr err; |
static const EventTypeSpec sApplicationEvents[] = { { kEventClassCommand, kEventCommandProcess } }; |
BlockZero( &g, sizeof(g) ); |
g.mainBundle = CFBundleGetMainBundle(); |
if ( g.mainBundle == NULL ) { err = -1; goto Bail; } |
if ( MPLibraryIsLoaded() == false ) { err = -1; goto Bail; } |
err = CreateNibReferenceWithCFBundle( g.mainBundle, CFSTR("main"), &g.mainNib ); |
if ( err != noErr ) goto Bail; |
if ( g.mainNib == NULL ) { err = -1; goto Bail; } |
err = SetMenuBarFromNib( g.mainNib, CFSTR("MenuBar") ); |
if ( err != noErr ) goto Bail; |
InstallApplicationEventHandler( NewEventHandlerUPP(AppEventEventHandlerProc), GetEventTypeCount(sApplicationEvents), sApplicationEvents, 0, NULL ); |
Bail: |
return( err ); |
} |
/***************************************************** |
* |
* DisplaySimpleWindow ( void ) |
* |
* Purpose: Called to create a new window in response to a kHICommandNew event. Here we create a window, set up the MyMPTaskInfo structure, |
* and create an MP thread to monitor our specified directories. |
* |
*/ |
static void DisplaySimpleWindow( void ) |
{ |
OSStatus err; |
OSStatus err1; |
WindowRef window; |
ControlRef control; |
MPTaskID mpTaskID; |
MyMPTaskInfo *mpTaskInfo; |
FSRef fsRef; |
char path[MAXPATHLEN]; |
DialogRef alertDialog; |
static EventHandlerUPP mpWindowEventHandlerUPP; |
SInt32 i = -1; |
const EventTypeSpec windowEvents[] = { |
{ kEventClassCommand, kEventCommandProcess }, |
{ kEventClassMP, kEventKQueue }, |
{ kEventClassWindow, kEventWindowClose } |
}; |
err = CreateWindowFromNib( g.mainNib, CFSTR("MainWindow"), &window ); |
if ( (err != noErr) || (window == NULL) ) goto Bail; |
if ( mpWindowEventHandlerUPP == NULL ) mpWindowEventHandlerUPP = NewEventHandlerUPP( MPWindowEventHandlerProc ); // MPWindowEventHandlerProc handles events for this window |
err = InstallWindowEventHandler( window, mpWindowEventHandlerUPP, GetEventTypeCount(windowEvents), windowEvents, window, NULL ); |
// Display the directories we are going to watch in the static text fields. In this sample we hard code the values to a few specific locations |
err = FSFindFolder( kUserDomain, kDesktopFolderType, kDontCreateFolder, &fsRef ); // Watch the Desktop folder |
err1 = FSRefMakePath( &fsRef, (UInt8 *)path, MAXPATHLEN ); |
if ( (err == noErr) && (err1 == noErr) ) |
SetControlCString( window, 'STxt', ++i, path ); |
err = FSFindFolder( kUserDomain, kDocumentsFolderType, kDontCreateFolder, &fsRef ); // Watch the Documents folder |
err1 = FSRefMakePath( &fsRef, (UInt8 *)path, MAXPATHLEN ); |
if ( (err == noErr) && (err1 == noErr) ) |
SetControlCString( window, 'STxt', ++i, path ); |
err = FSFindFolder( kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &fsRef ); // Watch the Users folder |
err1 = FSRefMakePath( &fsRef, (UInt8 *)path, MAXPATHLEN ); |
if ( (err == noErr) && (err1 == noErr) ) |
SetControlCString( window, 'STxt', ++i, path ); |
mpTaskInfo = (MyMPTaskInfo*) NewPtrClear( sizeof(MyMPTaskInfo) ); |
SetWRefCon( window, (long) mpTaskInfo ); |
for ( mpTaskInfo->count = 0 ; mpTaskInfo->count < kMaxFoldersToWatch ; mpTaskInfo->count++ ) |
{ |
GetControlCString( window, 'STxt', mpTaskInfo->count, mpTaskInfo->path[mpTaskInfo->count] ); // This code pretty much just reads back the strings we set above |
if ( mpTaskInfo->path[mpTaskInfo->count][0] == '\0' ) break; |
// We initialize a number of values which are not be safe to retrieve from an MP thread. |
GetControlBySigAndID( window, 'Date', mpTaskInfo->count, &mpTaskInfo->mpControlInfo[mpTaskInfo->count].dateControl ); |
GetControlBySigAndID( window, 'STxt', mpTaskInfo->count, &control ); |
mpTaskInfo->mpControlInfo[mpTaskInfo->count].eventTarget = GetControlEventTarget( control ); |
} |
if ( mpTaskInfo->count < 1 ) |
{ |
DisposePtr( (Ptr) mpTaskInfo ); |
goto Bail; |
} |
// Create our MP thread and pass in mpTaskInfo. MyMPTask is responsible for watching the passed in directories, and posting notifications of changes. |
err = MPCreateTask( MyMPTask, (void *) mpTaskInfo, 0, NULL, 0, 0, kNilOptions, &mpTaskID ); |
if ( err != noErr ) // Alert if an error occured |
{ |
CreateStandardAlert( kAlertStopAlert, CFSTR("MPCreateTask returned an error! Will not create window."), NULL, NULL, &alertDialog ); |
RunStandardAlert( alertDialog, NULL, NULL ); |
goto Bail; |
} |
ShowWindow( window ); |
Bail: |
return; |
} |
static pascal OSStatus AppEventEventHandlerProc( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ) |
{ |
#pragma unused ( inCallRef, inUserData ) |
HICommand command; |
OSStatus err = eventNotHandledErr; |
UInt32 eventClass = GetEventClass( inEvent ); |
UInt32 eventKind = GetEventKind(inEvent); |
switch ( eventClass ) |
{ |
case kEventClassCommand: |
GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command ); |
if ( eventKind == kEventCommandProcess ) |
{ |
if ( command.commandID == kHICommandNew ) |
{ |
DisplaySimpleWindow(); |
} |
} |
break; |
} |
return( err ); |
} |
/***************************************************** |
* |
* MPWindowEventHandlerProc ( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ) |
* |
* Purpose: Window event handling routine |
* |
* Returns: OSStatus - eventNotHandledErr to allow other EventHandlers to run |
*/ |
static pascal OSStatus MPWindowEventHandlerProc( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ) |
{ |
#pragma unused ( inCallRef, inUserData ) |
LongDateRec lDate; |
LongDateTime lSecs; |
unsigned long secs; |
ControlRef dateControl; |
WindowRef window; |
struct kevent kev; |
HICommand command; |
FSRef fsRef; |
CFURLRef urlRef = NULL; |
CFURLRef fullUrlRef = NULL; |
OSStatus err = eventNotHandledErr; |
UInt32 eventKind = GetEventKind( inEvent ); |
UInt32 eventClass = GetEventClass( inEvent ); |
switch ( eventClass ) |
{ |
case kEventClassMP: |
if ( eventKind == kEventKQueue ) |
{ |
// When we receive the kEventKQueue event, we update the date control associated with the path we are watching. |
GetEventParameter( inEvent, kEventParamDirectObject, typeKEvent, NULL, sizeof(struct kevent), NULL, &kev ); |
GetEventParameter( inEvent, kEventParamControlRef, typeControlRef, NULL, sizeof(ControlRef), NULL, &dateControl ); |
GetDateTime( &secs ); |
lSecs = secs; |
LongSecondsToDate( &lSecs, &lDate ); |
(void) SetControlData( dateControl, 0, kControlClockLongDateTag, sizeof(lDate), &lDate ); |
Draw1Control( dateControl ); |
PrintKEvent( GetControlOwner( dateControl ) , &kev ); // Display the kevent information |
} |
break; |
case kEventClassWindow: |
if ( eventKind == kEventWindowClose ) |
{ |
GetEventParameter( inEvent, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &window ); |
((MyMPTaskInfo*)GetWRefCon( window ))->done = true; // Flag the thread to terminate (thread checks at least every 30 seconds in this sample) |
} |
break; |
case kEventClassCommand: |
GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command ); |
if ( eventKind == kEventCommandProcess ) |
{ |
if ( command.commandID == 'Help' ) // Our 'Help' command, just have LaunchServices open the "kqueue.pdf" file within our bundle. |
{ |
urlRef = CFBundleCopyResourcesDirectoryURL( CFBundleGetMainBundle() ); |
fullUrlRef = CFURLCreateCopyAppendingPathComponent( NULL, urlRef, CFSTR("kqueue.pdf"), false ); |
CFURLGetFSRef( fullUrlRef, &fsRef ); |
(void) LSOpenFSRef( &fsRef, NULL ); // Open the file |
if ( urlRef != NULL ) CFRelease( urlRef ); |
if ( urlRef != NULL ) CFRelease( fullUrlRef ); |
} |
} |
break; |
} |
return( err ); |
} |
static void PrintKEvent( WindowRef window, struct kevent *kevp ) |
{ |
char s[512]; |
char descriptionText[512]; |
static char *const filter[] = { "EVFILT_UNKNOWN", /* 0 */ |
"EVFILT_READ", /* -1 */ |
"EVFILT_WRITE", /* -2 */ |
"EVFILT_AIO", /* -3 */ |
"EVFILT_VNODE", /* -4 */ |
"EVFILT_PROC", /* -5 */ |
"EVFILT_SIGNAL", /* -6 */ |
"EVFILT_TIMER", /* -7 */ |
"EVFILT_MACHPORT" /* -8 */ |
}; |
sprintf( descriptionText, "\tident=%d, filter=%s,", (int)kevp->ident, filter[-kevp->filter] ); |
if ( kevp->flags == 0 ) |
{ |
sprintf( s, " flags=0x0000, " ); |
strcat( descriptionText, s ); |
} |
else |
{ |
sprintf( s, " flags=0x%04x (%s%s%s%s%s%s%s%s), ", |
kevp->flags, |
(kevp->flags & EV_EOF) ? "EV_EOF" : "", |
(kevp->flags & EV_ERROR) ? ((kevp->flags & (~(EV_ERROR - 1) & ~EV_ERROR)) ? " | EV_ERROR" : "EV_ERROR") : "", |
(kevp->flags & EV_ADD) ? ((kevp->flags & (EV_SYSFLAGS | (EV_ADD - 1))) ? " | EV_ADD" : "EV_ADD") : "", |
(kevp->flags & EV_DELETE) ? ((kevp->flags & (EV_SYSFLAGS | (EV_DELETE - 1))) ? " | EV_DELETE" : "EV_DELETE") : "", |
(kevp->flags & EV_ENABLE) ? ((kevp->flags & (EV_SYSFLAGS | (EV_ENABLE - 1))) ? " | EV_ENABLE" : "EV_ENABLE") : "", |
(kevp->flags & EV_DISABLE) ? ((kevp->flags & (EV_SYSFLAGS | (EV_DISABLE - 1))) ? " | EV_DISABLE" : "EV_DISABLE") : "", |
(kevp->flags & EV_ONESHOT) ? ((kevp->flags & (EV_SYSFLAGS | (EV_ONESHOT - 1))) ? " | EV_ONESHOT" : "EV_ONESHOT") : "", |
(kevp->flags & EV_CLEAR) ? ((kevp->flags & (EV_SYSFLAGS | (EV_CLEAR - 1))) ? " | EV_CLEAR" : "EV_CLEAR") : "" ); |
strcat( descriptionText, s ); |
} |
sprintf( s, "fflags=0x%08x (%s%s%s%s%s%s%s)\n", |
kevp->fflags, |
(kevp->fflags & NOTE_DELETE) ? "NOTE_DELETE" : "", |
(kevp->fflags & NOTE_WRITE) ? ((kevp->fflags & (NOTE_WRITE - 1)) ? " | NOTE_WRITE" : "NOTE_WRITE") : "", |
(kevp->fflags & NOTE_EXTEND) ? ((kevp->fflags & (NOTE_EXTEND - 1)) ? " | NOTE_EXTEND" : "NOTE_EXTEND") : "", |
(kevp->fflags & NOTE_ATTRIB) ? ((kevp->fflags & (NOTE_ATTRIB - 1)) ? " | NOTE_ATTRIB" : "NOTE_ATTRIB") : "", |
(kevp->fflags & NOTE_LINK) ? ((kevp->fflags & (NOTE_LINK - 1)) ? " | NOTE_LINK" : "NOTE_LINK") : "", |
(kevp->fflags & NOTE_RENAME) ? ((kevp->fflags & (NOTE_RENAME - 1)) ? " | NOTE_RENAME" : "NOTE_RENAME") : "", |
(kevp->fflags & NOTE_REVOKE) ? ((kevp->fflags & (NOTE_REVOKE - 1)) ? " | NOTE_REVOKE" : "NOTE_REVOKE") : "" ); |
strcat( descriptionText, s ); |
sprintf( s, "\tdata = %d, udata = 0x%08lx\n", (int)kevp->data, (unsigned long)kevp->udata ); |
strcat( descriptionText, s ); |
(void) SetControlCString( window, 'STxt', 100, descriptionText ); |
} |
#pragma mark - |
#pragma mark ¥ Routines running on MP Thread ¥ |
/***************************************************** |
* |
* MyMPTask ( void *parameter ) |
* |
* Purpose: This routine is the entry point for our MP thread called from DisplaySimpleWindow(). It sets up the array of kevent |
* structures, and then loops while calling kevent(). The calls to kevent() block until one of the events, vnode_events, |
* we are interested in occurs to one of the file descriptors we are watching. After we receive notification, we then |
* post the information back to the main event queue for display. |
* |
* Inputs: parameter - MyMPTaskInfo* set up within DisplaySimpleWindow(). |
* |
* Returns: OSStatus - Error |
*/ |
static OSStatus MyMPTask( void *parameter ) |
{ |
int i; |
int kq; |
int ev_count; |
struct kevent *kevp; |
struct kevent ev_change[kMaxFoldersToWatch]; |
int fd[kMaxFoldersToWatch]; |
MyMPTaskInfo *mpTaskInfo = (MyMPTaskInfo *) parameter; // Our passed in data from DisplaySimpleWindow() |
OSStatus err = noErr; |
int foldersToWatch = 0; |
struct kevent ev_receive[kMaxFoldersToWatch] = { { 0 } }; |
struct timespec timeOut = { 30, 0 }; // Time out 30 seconds, we check mpTaskInfo->done at leaset every 30 seconds |
u_int vnode_events = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | // Events we are interested in watching |
NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE; |
if ( (kq = kqueue()) < 0 ) goto Bail; |
for ( foldersToWatch = 0 ; foldersToWatch < mpTaskInfo->count ; foldersToWatch++ ) |
{ |
// Currently the O_EVTONLY is designed so that to keep a dir file descriptor open without stopping users from unmounting the disk. ÊHFS+ only on 10.3 |
fd[foldersToWatch] = open( mpTaskInfo->path[foldersToWatch], O_EVTONLY ); // Open a descriptor for each directory we are watching |
if ( fd[foldersToWatch] <= 0 ) break; // If we get any errors, just break and continue with what we have |
EV_SET( &ev_change[foldersToWatch], fd[foldersToWatch], EVFILT_VNODE, EV_ADD | EV_CLEAR, vnode_events, 0, &(mpTaskInfo->mpControlInfo[foldersToWatch]) ); |
} |
// The main worker loop |
ev_count = kevent( kq, ev_change, foldersToWatch, ev_receive, kMaxFoldersToWatch, &timeOut ); // First call kevent specifying the number of folders to watch |
while ( (ev_count >= 0) && (mpTaskInfo->done != true) ) // If error, or window closed (setting done to true) fall through to Bail |
{ |
for ( i = 0 ; i < ev_count ; i++ ) |
{ |
kevp = &ev_receive[i]; |
if ( kevp->flags == EV_ERROR ) goto Bail; |
PostKQueueEvent( kevp ); // Post kevent |
} |
ev_count = kevent( kq, ev_change, 0, ev_receive, kMaxFoldersToWatch, &timeOut ); // Subsequent calls to kevent we will not be watching any additional file descriptors |
} |
Bail: |
for ( i = 0 ; i < foldersToWatch ; i++ ) // Clean up and close any open file descriptors |
(void) close( fd[i] ); |
DisposePtr( (Ptr) mpTaskInfo ); |
return( err ); |
} |
OSStatus PostKQueueEvent( struct kevent *kevp ) |
{ |
OSStatus err = noErr; |
EventRef event = NULL; |
MPControlInfo *mpControl = kevp->udata; |
err = CreateEvent( NULL, kEventClassMP, kEventKQueue, GetCurrentEventTime(), kEventAttributeNone, &event ); |
if ( err != noErr ) goto Bail; |
err = SetEventParameter( event, kEventParamDirectObject, typeKEvent, sizeof(struct kevent), kevp ); // Send the kevent |
if ( err != noErr ) goto Bail; |
err = SetEventParameter( event, kEventParamPostTarget, typeEventTargetRef, sizeof(void*), &mpControl->eventTarget ); // Target the date control |
if ( err != noErr ) goto Bail; |
err = SetEventParameter( event, kEventParamControlRef, typeControlRef, sizeof(ControlRef), &mpControl->dateControl ); // ControlRef to update |
if ( err != noErr ) goto Bail; |
err = PostEventToQueue( GetMainEventQueue(), event, kEventPriorityStandard ); // Post the event to the main event queue on the main thread |
Bail: |
if ( event != NULL ) (void) ReleaseEvent( event ); |
return( err ); |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-10-27