github.com/checksum/notify@v0.0.0-20190119234841-59aa2d88664f/watcher_fsevents_cgo.go (about) 1 // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. 2 // Use of this source code is governed by the MIT license that can be 3 // found in the LICENSE file. 4 5 // +build darwin,!kqueue 6 7 package notify 8 9 /* 10 #include <CoreServices/CoreServices.h> 11 12 typedef void (*CFRunLoopPerformCallBack)(void*); 13 14 void gosource(void *); 15 void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t); 16 17 static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { 18 context->info = (void*) info; 19 return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags); 20 } 21 22 #cgo LDFLAGS: -framework CoreServices 23 */ 24 import "C" 25 26 import ( 27 "errors" 28 "os" 29 "runtime" 30 "sync" 31 "sync/atomic" 32 "unsafe" 33 ) 34 35 var nilstream C.FSEventStreamRef 36 37 // Default arguments for FSEventStreamCreate function. 38 var ( 39 latency C.CFTimeInterval 40 flags = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer) 41 since = uint64(C.FSEventsGetCurrentEventId()) 42 ) 43 44 var runloop C.CFRunLoopRef // global runloop which all streams are registered with 45 var wg sync.WaitGroup // used to wait until the runloop starts 46 47 // source is used for synchronization purposes - it signals when runloop has 48 // started and is ready via the wg. It also serves purpose of a dummy source, 49 // thanks to it the runloop does not return as it also has at least one source 50 // registered. 51 var source = C.CFRunLoopSourceCreate(C.kCFAllocatorDefault, 0, &C.CFRunLoopSourceContext{ 52 perform: (C.CFRunLoopPerformCallBack)(C.gosource), 53 }) 54 55 // Errors returned when FSEvents functions fail. 56 var ( 57 errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL")) 58 errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false")) 59 ) 60 61 // initializes the global runloop and ensures any created stream awaits its 62 // readiness. 63 func init() { 64 wg.Add(1) 65 go func() { 66 // There is exactly one run loop per thread. Lock this goroutine to its 67 // thread to ensure that it's not rescheduled on a different thread while 68 // setting up the run loop. 69 runtime.LockOSThread() 70 runloop = C.CFRunLoopGetCurrent() 71 C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode) 72 C.CFRunLoopRun() 73 panic("runloop has just unexpectedly stopped") 74 }() 75 C.CFRunLoopSourceSignal(source) 76 } 77 78 //export gosource 79 func gosource(unsafe.Pointer) { 80 wg.Done() 81 } 82 83 //export gostream 84 func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) { 85 const ( 86 offchar = unsafe.Sizeof((*C.char)(nil)) 87 offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0)) 88 offid = unsafe.Sizeof(C.FSEventStreamEventId(0)) 89 ) 90 if n == 0 { 91 return 92 } 93 ev := make([]FSEvent, 0, int(n)) 94 for i := uintptr(0); i < uintptr(n); i++ { 95 switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); { 96 case flags&uint32(FSEventsEventIdsWrapped) != 0: 97 atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId())) 98 default: 99 ev = append(ev, FSEvent{ 100 Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))), 101 Flags: flags, 102 ID: *(*uint64)(unsafe.Pointer(ids + i*offid)), 103 }) 104 } 105 106 } 107 streamFuncs.get(info)(ev) 108 } 109 110 // StreamFunc is a callback called when stream receives file events. 111 type streamFunc func([]FSEvent) 112 113 var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}} 114 115 type streamFuncRegistry struct { 116 mu sync.Mutex 117 m map[uintptr]streamFunc 118 i uintptr 119 } 120 121 func (r *streamFuncRegistry) get(id uintptr) streamFunc { 122 r.mu.Lock() 123 defer r.mu.Unlock() 124 return r.m[id] 125 } 126 127 func (r *streamFuncRegistry) add(fn streamFunc) uintptr { 128 r.mu.Lock() 129 defer r.mu.Unlock() 130 r.i++ 131 r.m[r.i] = fn 132 return r.i 133 } 134 135 func (r *streamFuncRegistry) delete(id uintptr) { 136 r.mu.Lock() 137 defer r.mu.Unlock() 138 delete(r.m, id) 139 } 140 141 // Stream represents single watch-point which listens for events scheduled by 142 // the global runloop. 143 type stream struct { 144 path string 145 ref C.FSEventStreamRef 146 info uintptr 147 } 148 149 // NewStream creates a stream for given path, listening for file events and 150 // calling fn upon receiving any. 151 func newStream(path string, fn streamFunc) *stream { 152 return &stream{ 153 path: path, 154 info: streamFuncs.add(fn), 155 } 156 } 157 158 // Start creates a FSEventStream for the given path and schedules it with 159 // global runloop. It's a nop if the stream was already started. 160 func (s *stream) Start() error { 161 if s.ref != nilstream { 162 return nil 163 } 164 wg.Wait() 165 p := C.CFStringCreateWithCStringNoCopy(C.kCFAllocatorDefault, C.CString(s.path), C.kCFStringEncodingUTF8, C.kCFAllocatorDefault) 166 path := C.CFArrayCreate(C.kCFAllocatorDefault, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil) 167 ctx := C.FSEventStreamContext{} 168 ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags) 169 if ref == nilstream { 170 return errCreate 171 } 172 C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode) 173 if C.FSEventStreamStart(ref) == C.Boolean(0) { 174 C.FSEventStreamInvalidate(ref) 175 return errStart 176 } 177 C.CFRunLoopWakeUp(runloop) 178 s.ref = ref 179 return nil 180 } 181 182 // Stop stops underlying FSEventStream and unregisters it from global runloop. 183 func (s *stream) Stop() { 184 if s.ref == nilstream { 185 return 186 } 187 wg.Wait() 188 C.FSEventStreamStop(s.ref) 189 C.FSEventStreamInvalidate(s.ref) 190 C.CFRunLoopWakeUp(runloop) 191 s.ref = nilstream 192 streamFuncs.delete(s.info) 193 }