Skip to content

Instantly share code, notes, and snippets.

@jparishy
Created December 14, 2013 00:00
Show Gist options
  • Select an option

  • Save jparishy/7953819 to your computer and use it in GitHub Desktop.

Select an option

Save jparishy/7953819 to your computer and use it in GitHub Desktop.
Watcher
//
// main.m
// FSEventsTest
//
// Created by Julius Parishy on 8/20/13.
// Copyright (c) 2013 Fitocracy. All rights reserved.
//
#import <Foundation/Foundation.h>
NSString *LEFSEventDescription(FSEventStreamEventFlags flags, NSString *path)
{
BOOL(^flags_has)(FSEventStreamEventFlags, FSEventStreamEventFlags) = ^BOOL(FSEventStreamEventFlags flags, FSEventStreamEventFlags flag){
return ((flags & flag) != 0);
};
NSString *type = @"Unknown Type";
if(flags_has(flags,kFSEventStreamEventFlagItemIsFile))
{
type = @"File";
}
else if(flags_has(flags, kFSEventStreamEventFlagItemIsDir))
{
type = @"Directory";
}
else if(flags_has(flags, kFSEventStreamEventFlagItemIsSymlink))
{
type = @"Symbolic Link";
}
NSMutableString *action = [@"" mutableCopy];
NSDictionary *flagsMap = @{
@(kFSEventStreamEventFlagItemCreated) : @"ItemCreated",
@(kFSEventStreamEventFlagItemRemoved) : @"ItemRemoved",
@(kFSEventStreamEventFlagItemInodeMetaMod) : @"ItemInodeMetaMod",
@(kFSEventStreamEventFlagItemRenamed) : @"ItemRenamed",
@(kFSEventStreamEventFlagItemModified) : @"ItemModified",
@(kFSEventStreamEventFlagItemFinderInfoMod) : @"ItemFinderInfoMod",
@(kFSEventStreamEventFlagItemChangeOwner) : @"ItemChangeOwner",
@(kFSEventStreamEventFlagItemXattrMod) : @"@ItemXattrMod",
@(kFSEventStreamEventFlagItemIsFile) : @"ItemIsFile",
@(kFSEventStreamEventFlagItemIsDir) : @"ItemIsDir",
@(kFSEventStreamEventFlagItemIsSymlink) : @"ItemIsSymlink"
};
[flagsMap enumerateKeysAndObjectsUsingBlock:^(NSNumber *flag, NSString *value, BOOL *stop) {
if(flags_has(flags, flag.unsignedIntValue))
{
[action appendFormat:@"%@,", value];
}
}];
return [NSString stringWithFormat:@"%@ - %@: %@", type, action, path];
}
@interface LEDirectoryWatcher : NSObject
@property (nonatomic, assign, readonly) FSEventStreamRef stream;
@property (nonatomic, strong, readonly) NSArray *directories;
@property (nonatomic, assign) NSTimeInterval updateInterval;
- (id)initWithDirectories:(NSArray *)directories;
- (void)begin;
- (void)end;
- (void)handleCallbackWithEventPaths:(NSArray *)paths flags:(NSArray *)eventFlags;
- (void)handlePath:(NSString *)path flags:(FSEventStreamEventFlags)flags;
@end
void LEFSEventCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
{
const char **castedPaths = eventPaths;
NSMutableArray *paths = [NSMutableArray array];
NSMutableArray *flags = [NSMutableArray array];
for(NSInteger i = 0; i < numEvents; ++i)
{
const char *pathCString = (const char *)castedPaths[i];
NSString *path = [NSString stringWithCString:pathCString encoding:NSUTF8StringEncoding];
[paths addObject:path];
[flags addObject:@(eventFlags[i])];
}
LEDirectoryWatcher *watcher = (__bridge LEDirectoryWatcher *)clientCallBackInfo;
[watcher handleCallbackWithEventPaths:[paths copy] flags:[flags copy]];
}
@implementation LEDirectoryWatcher
- (id)initWithDirectories:(NSArray *)directories
{
if((self = [super init]))
{
_directories = [directories copy];
self.updateInterval = 1.0f;
}
return self;
}
- (void)begin
{
FSEventStreamEventId latestEventId = kFSEventStreamEventIdSinceNow;
FSEventStreamContext context = { 0 };
context.info = (__bridge void *)self;
CFArrayRef paths = (__bridge CFArrayRef)self.directories;
_stream = FSEventStreamCreate(kCFAllocatorDefault, &LEFSEventCallback, &context, paths, latestEventId, self.updateInterval, kFSEventStreamCreateFlagFileEvents);
FSEventStreamScheduleWithRunLoop(self.stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
FSEventStreamStart(self.stream);
}
- (void)end
{
FSEventStreamStop(self.stream);
FSEventStreamInvalidate(self.stream);
FSEventStreamRelease(self.stream);
}
- (void)handleCallbackWithEventPaths:(NSArray *)paths flags:(NSArray *)eventFlags
{
[paths enumerateObjectsUsingBlock:^(NSString *path, NSUInteger idx, BOOL *stop) {
BOOL included = NO;
for(NSString *directory in self.directories)
{
if([path hasPrefix:directory])
{
included = YES;
break;
}
}
if(included)
{
FSEventStreamEventFlags flags = [eventFlags[idx] unsignedIntValue];
[self handlePath:path flags:flags];
}
}];
}
- (void)handlePath:(NSString *)path flags:(FSEventStreamEventFlags)flags
{
NSLog(@"%@", LEFSEventDescription(flags, path));
}
@end
@interface LEFileUpdater : LEDirectoryWatcher
@property (nonatomic, strong) NSDictionary *commandsByFilename;
@end
@implementation LEFileUpdater
- (void)handlePath:(NSString *)path flags:(FSEventStreamEventFlags)flags
{
[self.commandsByFilename enumerateKeysAndObjectsUsingBlock:^(NSString *filename, NSString *command, BOOL *stop) {
if([path isEqualToString:filename])
{
[self runCommand:command forFilename:filename];
}
}];
}
- (void)runCommand:(NSString *)command forFilename:(NSString *)filename
{
NSArray *components = [command componentsSeparatedByString:@" "];
NSTask *task = [[NSTask alloc] init];
task.launchPath = components[0];
task.arguments = [components subarrayWithRange:NSMakeRange(1, components.count - 1)];
//NSLog(@"%@", task.launchPath);
// NSLog(@"%@", task.arguments);
NSPipe *outputPipe = [[NSPipe alloc] init];
task.standardOutput = outputPipe;
[task launch];
//NSLog(@"Performed '%@' for '%@'", components, filename);
}
@end
BOOL running = YES;
void sigint_callback(int signal)
{
running = NO;
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
signal(SIGINT, sigint_callback);
const char *directoryArg = argv[1];
NSString *directory = [NSString stringWithCString:directoryArg encoding:NSUTF8StringEncoding];
LEFileUpdater *watcher = [[LEFileUpdater alloc] initWithDirectories:@[ directory ]];
NSMutableDictionary *commandsByFilename = [NSMutableDictionary dictionary];
for(NSInteger i = 2; i < argc; i += 2)
{
const char *filenameArg = argv[i + 0];
const char *commandArg = argv[i + 1];
NSString *filename = [NSString stringWithCString:filenameArg encoding:NSUTF8StringEncoding];
NSString *command = [NSString stringWithCString:commandArg encoding:NSUTF8StringEncoding];
commandsByFilename[filename] = command;
}
watcher.commandsByFilename = [commandsByFilename copy];
[watcher begin];
while(running)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate.date dateByAddingTimeInterval:1.0f]];
}
[watcher end];
printf("\n\nQuiting.\n");
}
return 0;
}
➜ Desktop clang fs.m -framework Foundation -framework CoreServices -o watch
➜ Desktop ./watch "/full/path/to/dir" "/full/path/to/dir/file/to/watch.txt" "/usr/bin/say changed"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment