watcher_fsevents_cgo.go 5.3 KB

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