public
Last active

A wrapper around kqueue to monitor changes to files. Works on iOS.

  • Download Gist
gistfile1.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
#import "FileChangeObserver.h"
 
#undef Assert
#define Assert(COND) { if (!(COND)) { raise( SIGINT ) ; } }
 
@interface FileChangeObserver ()
@property ( nonatomic, readonly ) int kqueue ;
@property ( nonatomic ) enum FileChangeNotificationType typeMask ;
@end
 
@implementation FileChangeObserver
@synthesize kqueue = _kqueue ;
 
+(instancetype)observerForURL:(NSURL*)url types:(enum FileChangeNotificationType)types delegate:(id<FileChangeObserverDelegate>)delegate
{
if ( !url ) { return nil ; }
FileChangeObserver * result = [ [ [ self class ] alloc ] init ] ;
result.url = url ;
result.delegate = delegate ;
result.typeMask = types ;
[ result startObserving ] ;
return result ;
}
 
-(void)dealloc
{
printf("%s\n", __PRETTY_FUNCTION__);
[ self stopObserving ] ;
}
 
 
//
// kqueue_main
//
 
static void (^kqueue_main)(FileChangeObserver *) = ^(__unsafe_unretained FileChangeObserver * self) {
int fd = open( [ [ self.url path ] fileSystemRepresentation ], O_EVTONLY ) ;
Assert( fd >= 0 );
int q = self.kqueue ;
{
struct kevent event = {
.ident = fd,
.filter = EVFILT_VNODE,
.flags = EV_ADD | EV_CLEAR,
.fflags = self.typeMask,
} ;
int error = kevent( q, &event, 1, NULL, 0, NULL);
Assert(error == 0);
}
struct kevent event = {0} ;
for(;;)
{
int nEvents = kevent( q, NULL, 0, &event, 1, NULL ) ;
if ( nEvents != 1 ) { break ; }
[[ NSThread mainThread ] perform:^{
[ self.delegate fileChanged:self typeMask:(enum FileChangeNotificationType)event.fflags ] ;
}];
}
} ;
 
 
-(void)stopObserving
{
@synchronized( self )
{
close( self.kqueue ) ;
}
}
 
-(void)invalidate
{
[ self stopObserving ] ;
}
 
-(void)startObserving
{
@synchronized( self )
{
printf("%s\n", __PRETTY_FUNCTION__);
static dispatch_queue_t __q ;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__q = dispatch_queue_create( "file observer queue", DISPATCH_QUEUE_CONCURRENT ) ;
});
}
// this is __unsafe_unretained to avoid retaining 'self'.
// using __weak isn't a strong enough to prevent ARC from retaining 'self'.
// This allows the observer to be torn down simply be releasing it,
// instead of requiring the client to invoke -invalidate.
__unsafe_unretained id unsafe_self = self ;
dispatch_async( __q, ^{
kqueue_main( unsafe_self ) ;
}) ;
}
}
 
-(int)kqueue
{
if ( !_kqueue ) { _kqueue = kqueue() ; }
return _kqueue ;
}
 
@end
gistfile2.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#import <Foundation/Foundation.h>
 
enum FileChangeNotificationType
{
kFileChangeType_Delete = NOTE_DELETE
, kFileChangeType_Write = NOTE_WRITE
, kFileChangeType_DirectoryContentsChanged = kFileChangeType_Write
//#define NOTE_WRITE 0x00000002 /* data contents changed */
//#define NOTE_EXTEND 0x00000004 /* size increased */
//#define NOTE_ATTRIB 0x00000008 /* attributes changed */
//#define NOTE_LINK 0x00000010 /* link count changed */
//#define NOTE_RENAME 0x00000020 /* vnode was renamed */
//#define NOTE_REVOKE 0x00000040 /* vnode access was revoked */
//#define NOTE_NONE 0x00000080 /* No specific vnode event: to test for EVFILT_READ activation*/
 
} ;
 
@class FileChangeObserver ;
 
@protocol FileChangeObserverDelegate<NSObject>
@required
-(void)fileChanged:(FileChangeObserver*)observer typeMask:(enum FileChangeNotificationType)type ;
@end
 
@interface FileChangeObserver : NSObject
 
@property ( nonatomic, copy ) NSURL * url ;
@property ( nonatomic, weak ) id<FileChangeObserverDelegate> delegate ;
 
+(instancetype)observerForURL:(NSURL*)url types:(enum FileChangeNotificationType)types delegate:(id<FileChangeObserverDelegate>)delegate ;
-(void)invalidate ;
@end
gistfile3.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// category for NSString to print kqueue event flags
 
@implementation NSString (FileChangeObserver)
 
+(NSString*)stringWithKEventFFlags:(int)flags
{
NSMutableArray * array = [ NSMutableArray array ] ;
struct
{
__unsafe_unretained NSString * name;
} bitNames[] = {
{ @"NOTE_DELETE" }
, { @"NOTE_WRITE" }
, { @"NOTE_EXTEND" }
, { @"NOTE_ATTRIB" }
, { @"NOTE_LINK" }
, { @"NOTE_RENAME" }
, { @"NOTE_REVOKE" }
, { @"NOTE_NONE" }
} ;
for( int index = 0, count = COUNTOF( bitNames ); index < count; ++index )
{
if ( ( flags & ( 1 << index )) != 0 )
{
[ array addObject:bitNames[ index ].name ] ;
}
}
NSString * result = [ array componentsJoinedByString:@" " ] ;
return result ;
}
 
@end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.