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