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  }