wrap.go (175 lines of code) (raw):

// +build darwin package fsevents /* #cgo LDFLAGS: -framework CoreServices #include <CoreServices/CoreServices.h> #include <sys/stat.h> static CFArrayRef ArrayCreateMutable(int len) { return CFArrayCreateMutable(NULL, len, &kCFTypeArrayCallBacks); } extern void fsevtCallback(FSEventStreamRef p0, uintptr_t info, size_t p1, char** p2, FSEventStreamEventFlags* p3, FSEventStreamEventId* p4); static FSEventStreamRef EventStreamCreateRelativeToDevice(FSEventStreamContext * context, uintptr_t info, dev_t dev, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { context->info = (void*) info; return FSEventStreamCreateRelativeToDevice(NULL, (FSEventStreamCallback) fsevtCallback, context, dev, paths, since, latency, flags); } static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { context->info = (void*) info; return FSEventStreamCreate(NULL, (FSEventStreamCallback) fsevtCallback, context, paths, since, latency, flags); } */ import "C" import ( "fmt" "log" "path/filepath" "reflect" "runtime" "time" "unsafe" ) // LatestEventID returns the most recently generated event ID, system-wide. func LatestEventID() uint64 { return uint64(C.FSEventsGetCurrentEventId()) } // arguments are released by C at the end of the callback. Ensure copies // are made if data is expected to persist beyond this function ending. // //export fsevtCallback func fsevtCallback(stream C.FSEventStreamRef, info uintptr, numEvents C.size_t, cpaths **C.char, cflags *C.FSEventStreamEventFlags, cids *C.FSEventStreamEventId) { l := int(numEvents) events := make([]Event, l) es := registry.Get(info) if es == nil { log.Printf("failed to retrieve registry %d", info) return } // These slices are backed by C data. Ensure data is copied out // if it expected to exist outside of this function. paths := (*[1 << 30]*C.char)(unsafe.Pointer(cpaths))[:l:l] ids := (*[1 << 30]C.FSEventStreamEventId)(unsafe.Pointer(cids))[:l:l] flags := (*[1 << 30]C.FSEventStreamEventFlags)(unsafe.Pointer(cflags))[:l:l] for i := range events { events[i] = Event{ Path: C.GoString(paths[i]), Flags: EventFlags(flags[i]), ID: uint64(ids[i]), } es.EventID = uint64(ids[i]) } es.Events <- events } // FSEventStreamRef wraps C.FSEventStreamRef type FSEventStreamRef C.FSEventStreamRef // GetStreamRefEventID retrieves the last EventID from the ref func GetStreamRefEventID(f FSEventStreamRef) uint64 { return uint64(C.FSEventStreamGetLatestEventId(f)) } // GetStreamRefDeviceID retrieves the device ID the stream is watching func GetStreamRefDeviceID(f FSEventStreamRef) int32 { return int32(C.FSEventStreamGetDeviceBeingWatched(f)) } // GetStreamRefDescription retrieves debugging description information // about the StreamRef func GetStreamRefDescription(f FSEventStreamRef) string { return cfStringToGoString(C.FSEventStreamCopyDescription(f)) } // GetStreamRefPaths returns a copy of the paths being watched by // this stream func GetStreamRefPaths(f FSEventStreamRef) []string { arr := C.FSEventStreamCopyPathsBeingWatched(f) l := cfArrayLen(arr) ss := make([]string, l) for i := range ss { void := C.CFArrayGetValueAtIndex(arr, C.CFIndex(i)) ss[i] = cfStringToGoString(C.CFStringRef(void)) } return ss } func cfStringToGoString(cfs C.CFStringRef) string { if cfs == nullCFStringRef { return "" } cfStr := copyCFString(cfs) length := C.CFStringGetLength(cfStr) if length == 0 { // short-cut for empty strings return "" } cfRange := C.CFRange{0, length} enc := C.CFStringEncoding(C.kCFStringEncodingUTF8) // first find the buffer size necessary var usedBufLen C.CFIndex if C.CFStringGetBytes(cfStr, cfRange, enc, 0, C.false, nil, 0, &usedBufLen) == 0 { return "" } bs := make([]byte, usedBufLen) buf := (*C.UInt8)(unsafe.Pointer(&bs[0])) if C.CFStringGetBytes(cfStr, cfRange, enc, 0, C.false, buf, usedBufLen, nil) == 0 { return "" } // Create a string (byte array) backed by C byte array header := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) strHeader := &reflect.StringHeader{ Data: header.Data, Len: header.Len, } return *(*string)(unsafe.Pointer(strHeader)) } // copyCFString makes an immutable copy of a string with CFStringCreateCopy. func copyCFString(cfs C.CFStringRef) C.CFStringRef { return C.CFStringCreateCopy(C.kCFAllocatorDefault, cfs) } // CFRunLoopRef wraps C.CFRunLoopRef type CFRunLoopRef C.CFRunLoopRef // EventIDForDeviceBeforeTime returns an event ID before a given time. func EventIDForDeviceBeforeTime(dev int32, before time.Time) uint64 { tm := C.CFAbsoluteTime(before.Unix()) return uint64(C.FSEventsGetLastEventIdForDeviceBeforeTime(C.dev_t(dev), tm)) } // createPaths accepts the user defined set of paths and returns FSEvents // compatible array of paths func createPaths(paths []string) (C.CFArrayRef, error) { cPaths := C.ArrayCreateMutable(C.int(len(paths))) var errs []error for _, path := range paths { p, err := filepath.Abs(path) if err != nil { // hack up some reporting errors, but don't prevent execution // because of them errs = append(errs, err) } str := makeCFString(p) C.CFArrayAppendValue(C.CFMutableArrayRef(cPaths), unsafe.Pointer(str)) } var err error if len(errs) > 0 { err = fmt.Errorf("%q", errs) } return cPaths, err } // makeCFString makes an immutable string with CFStringCreateWithCString. func makeCFString(str string) C.CFStringRef { s := C.CString(str) defer C.free(unsafe.Pointer(s)) return C.CFStringCreateWithCString(C.kCFAllocatorDefault, s, C.kCFStringEncodingUTF8) } // CFArrayLen retrieves the length of CFArray type // See https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFArrayRef/#//apple_ref/c/func/CFArrayGetCount func cfArrayLen(ref C.CFArrayRef) int { // FIXME: this will probably crash on 32bit, untested // requires OS X v10.0 return int(C.CFArrayGetCount(ref)) } func setupStream(paths []string, flags CreateFlags, callbackInfo uintptr, eventID uint64, latency time.Duration, deviceID int32) FSEventStreamRef { cPaths, err := createPaths(paths) if err != nil { log.Printf("Error creating paths: %s", err) } defer C.CFRelease(C.CFTypeRef(cPaths)) since := C.FSEventStreamEventId(eventID) context := C.FSEventStreamContext{} info := C.uintptr_t(callbackInfo) cfinv := C.CFTimeInterval(float64(latency) / float64(time.Second)) var ref C.FSEventStreamRef if deviceID != 0 { ref = C.EventStreamCreateRelativeToDevice(&context, info, C.dev_t(deviceID), cPaths, since, cfinv, C.FSEventStreamCreateFlags(flags)) } else { ref = C.EventStreamCreate(&context, info, cPaths, since, cfinv, C.FSEventStreamCreateFlags(flags)) } return FSEventStreamRef(ref) } func (es *EventStream) start(paths []string, callbackInfo uintptr) { since := eventIDSinceNow if es.Resume { since = es.EventID } es.stream = setupStream(paths, es.Flags, callbackInfo, since, es.Latency, es.Device) started := make(chan struct{}) go func() { runtime.LockOSThread() es.rlref = CFRunLoopRef(C.CFRunLoopGetCurrent()) C.CFRetain(C.CFTypeRef(es.rlref)) C.FSEventStreamScheduleWithRunLoop(es.stream, C.CFRunLoopRef(es.rlref), C.kCFRunLoopDefaultMode) C.FSEventStreamStart(es.stream) close(started) C.CFRunLoopRun() }() if !es.hasFinalizer { // TODO: There is no guarantee this run before program exit // and could result in panics at exit. runtime.SetFinalizer(es, finalizer) es.hasFinalizer = true } <-started } func finalizer(es *EventStream) { // If an EventStream is freed without Stop being called it will // cause a panic. This avoids that, and closes the stream instead. es.Stop() } // flush drains the event stream of undelivered events func flush(stream FSEventStreamRef, sync bool) { if sync { C.FSEventStreamFlushSync(stream) } else { C.FSEventStreamFlushAsync(stream) } } // stop requests fsevents stops streaming events func stop(stream FSEventStreamRef, rlref CFRunLoopRef) { C.FSEventStreamStop(stream) C.FSEventStreamInvalidate(stream) C.FSEventStreamRelease(stream) C.CFRunLoopStop(C.CFRunLoopRef(rlref)) C.CFRelease(C.CFTypeRef(rlref)) }