github.com/dgallion1/notify@v0.9.3-0.20201128171805-931189d936e0/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,cgo
     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  	fn := streamFuncs.get(info)
    94  	if fn == nil {
    95  		return
    96  	}
    97  	ev := make([]FSEvent, 0, int(n))
    98  	for i := uintptr(0); i < uintptr(n); i++ {
    99  		switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
   100  		case flags&uint32(FSEventsEventIdsWrapped) != 0:
   101  			atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
   102  		default:
   103  			ev = append(ev, FSEvent{
   104  				Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
   105  				Flags: flags,
   106  				ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)),
   107  			})
   108  		}
   109  
   110  	}
   111  	fn(ev)
   112  }
   113  
   114  // StreamFunc is a callback called when stream receives file events.
   115  type streamFunc func([]FSEvent)
   116  
   117  var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
   118  
   119  type streamFuncRegistry struct {
   120  	mu sync.Mutex
   121  	m  map[uintptr]streamFunc
   122  	i  uintptr
   123  }
   124  
   125  func (r *streamFuncRegistry) get(id uintptr) streamFunc {
   126  	r.mu.Lock()
   127  	defer r.mu.Unlock()
   128  	return r.m[id]
   129  }
   130  
   131  func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
   132  	r.mu.Lock()
   133  	defer r.mu.Unlock()
   134  	r.i++
   135  	r.m[r.i] = fn
   136  	return r.i
   137  }
   138  
   139  func (r *streamFuncRegistry) delete(id uintptr) {
   140  	r.mu.Lock()
   141  	defer r.mu.Unlock()
   142  	delete(r.m, id)
   143  }
   144  
   145  // Stream represents single watch-point which listens for events scheduled by
   146  // the global runloop.
   147  type stream struct {
   148  	path string
   149  	ref  C.FSEventStreamRef
   150  	info uintptr
   151  }
   152  
   153  // NewStream creates a stream for given path, listening for file events and
   154  // calling fn upon receiving any.
   155  func newStream(path string, fn streamFunc) *stream {
   156  	return &stream{
   157  		path: path,
   158  		info: streamFuncs.add(fn),
   159  	}
   160  }
   161  
   162  // Start creates a FSEventStream for the given path and schedules it with
   163  // global runloop. It's a nop if the stream was already started.
   164  func (s *stream) Start() error {
   165  	if s.ref != nilstream {
   166  		return nil
   167  	}
   168  	wg.Wait()
   169  	p := C.CFStringCreateWithCStringNoCopy(C.kCFAllocatorDefault, C.CString(s.path), C.kCFStringEncodingUTF8, C.kCFAllocatorDefault)
   170  	path := C.CFArrayCreate(C.kCFAllocatorDefault, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
   171  	ctx := C.FSEventStreamContext{}
   172  	ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
   173  	if ref == nilstream {
   174  		return errCreate
   175  	}
   176  	C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
   177  	if C.FSEventStreamStart(ref) == C.Boolean(0) {
   178  		C.FSEventStreamInvalidate(ref)
   179  		return errStart
   180  	}
   181  	C.CFRunLoopWakeUp(runloop)
   182  	s.ref = ref
   183  	return nil
   184  }
   185  
   186  // Stop stops underlying FSEventStream and unregisters it from global runloop.
   187  func (s *stream) Stop() {
   188  	if s.ref == nilstream {
   189  		return
   190  	}
   191  	wg.Wait()
   192  	C.FSEventStreamStop(s.ref)
   193  	C.FSEventStreamInvalidate(s.ref)
   194  	C.CFRunLoopWakeUp(runloop)
   195  	s.ref = nilstream
   196  	streamFuncs.delete(s.info)
   197  }