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  }