github.com/DiversionCompany/notify@v0.9.9/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 //go:build darwin && !kqueue && cgo 6 // +build darwin,!kqueue,cgo 7 8 package notify 9 10 // See https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate 11 12 /* 13 #include <CoreServices/CoreServices.h> 14 #include <dispatch/dispatch.h> 15 16 void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t); 17 18 static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { 19 context->info = (void*) info; 20 return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags); 21 } 22 23 #cgo LDFLAGS: -framework CoreServices 24 */ 25 import "C" 26 27 import ( 28 "errors" 29 "os" 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 // global dispatch queue which all streams are registered with 45 var q C.dispatch_queue_t = C.dispatch_queue_create( 46 C.CString("com.github.rjeczalik.notify"), 47 (C.dispatch_queue_attr_t)(C.DISPATCH_QUEUE_SERIAL), 48 ) 49 50 // Errors returned when FSEvents functions fail. 51 var ( 52 errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL")) 53 errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false")) 54 ) 55 56 //export gostream 57 func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) { 58 const ( 59 offchar = unsafe.Sizeof((*C.char)(nil)) 60 offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0)) 61 offid = unsafe.Sizeof(C.FSEventStreamEventId(0)) 62 ) 63 if n == 0 { 64 return 65 } 66 fn := streamFuncs.get(info) 67 if fn == nil { 68 return 69 } 70 ev := make([]FSEvent, 0, int(n)) 71 for i := uintptr(0); i < uintptr(n); i++ { 72 switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); { 73 case flags&uint32(FSEventsEventIdsWrapped) != 0: 74 atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId())) 75 default: 76 ev = append(ev, FSEvent{ 77 Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))), 78 Flags: flags, 79 ID: *(*uint64)(unsafe.Pointer(ids + i*offid)), 80 }) 81 } 82 83 } 84 fn(ev) 85 } 86 87 // StreamFunc is a callback called when stream receives file events. 88 type streamFunc func([]FSEvent) 89 90 var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}} 91 92 type streamFuncRegistry struct { 93 mu sync.Mutex 94 m map[uintptr]streamFunc 95 i uintptr 96 } 97 98 func (r *streamFuncRegistry) get(id uintptr) streamFunc { 99 r.mu.Lock() 100 defer r.mu.Unlock() 101 return r.m[id] 102 } 103 104 func (r *streamFuncRegistry) add(fn streamFunc) uintptr { 105 r.mu.Lock() 106 defer r.mu.Unlock() 107 r.i++ 108 r.m[r.i] = fn 109 return r.i 110 } 111 112 func (r *streamFuncRegistry) delete(id uintptr) { 113 r.mu.Lock() 114 defer r.mu.Unlock() 115 delete(r.m, id) 116 } 117 118 // Stream represents a single watch-point which listens for events scheduled on the global dispatch queue. 119 type stream struct { 120 path string 121 ref C.FSEventStreamRef 122 info uintptr 123 } 124 125 // NewStream creates a stream for given path, listening for file events and 126 // calling fn upon receiving any. 127 func newStream(path string, fn streamFunc) *stream { 128 return &stream{ 129 path: path, 130 info: streamFuncs.add(fn), 131 } 132 } 133 134 // Start creates a FSEventStream for the given path and schedules on the global dispatch queue. 135 // It's a nop if the stream was already started. 136 func (s *stream) Start() error { 137 if s.ref != nilstream { 138 return nil 139 } 140 p := C.CFStringCreateWithCStringNoCopy(C.kCFAllocatorDefault, C.CString(s.path), C.kCFStringEncodingUTF8, C.kCFAllocatorDefault) 141 path := C.CFArrayCreate(C.kCFAllocatorDefault, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil) 142 ctx := C.FSEventStreamContext{} 143 ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags) 144 if ref == nilstream { 145 return errCreate 146 } 147 C.FSEventStreamSetDispatchQueue(ref, q) 148 if C.FSEventStreamStart(ref) == C.Boolean(0) { 149 C.FSEventStreamInvalidate(ref) 150 return errStart 151 } 152 s.ref = ref 153 return nil 154 } 155 156 // Stop stops underlying FSEventStream and unregisters it from the global dispatch queue. 157 func (s *stream) Stop() { 158 if s.ref == nilstream { 159 return 160 } 161 C.FSEventStreamStop(s.ref) 162 C.FSEventStreamInvalidate(s.ref) 163 s.ref = nilstream 164 streamFuncs.delete(s.info) 165 }