vendor tsgo
This commit is contained in:
640
tools/tsgo/internal/fswatch/fsevents_darwin.go
Normal file
640
tools/tsgo/internal/fswatch/fsevents_darwin.go
Normal file
@@ -0,0 +1,640 @@
|
||||
//go:build darwin && (amd64 || arm64)
|
||||
|
||||
package fswatch
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fsevents_darwin.go: macOS FSEvents backend (event processing)
|
||||
//
|
||||
// Uses Apple's FSEvents API to receive file-level notifications for watched
|
||||
// directory trees. FSEvents is a high-level, path-based API that watches
|
||||
// recursively without requiring an fd per file (unlike kqueue). Events are
|
||||
// coalesced by the kernel and delivered in batches.
|
||||
//
|
||||
// This file contains the event classification and stream lifecycle logic.
|
||||
// The low-level FFI plumbing (cgo-free CoreFoundation/CoreServices calls,
|
||||
// assembly trampolines, pipe-based callback synchronization) lives in
|
||||
// fsevents_darwin_ffi.go and the companion .s files.
|
||||
//
|
||||
// ┌───────────────────────────────────────────────────────────┐
|
||||
// │ fsEventsBackend │
|
||||
// │ (no event loop; start() just signals readiness) │
|
||||
// │ │
|
||||
// │ subscribe/closeWatch rebuild the shared stream set: │
|
||||
// │ │ │
|
||||
// │ ▼ │
|
||||
// │ ┌─────────────────────────────────────────────────────┐ │
|
||||
// │ │ fseventsStream[] │ │
|
||||
// │ │ │ │
|
||||
// │ │ each FSEventStream watches up to N paths ────────► │ │
|
||||
// │ │ per-stream GCD dispatch queue │ │
|
||||
// │ │ (UseCFTypes | FileEvents = 0x11) │ │
|
||||
// │ │ │ │
|
||||
// │ │ callback fires on GCD thread: │ │
|
||||
// │ │ ┌─────────────────────────────────────────┐ │ │
|
||||
// │ │ │ asm: retain/copy callback payload │ │ │
|
||||
// │ │ │ asm: write(eventPipe) ──────────────► │ │ │
|
||||
// │ │ │ eventLoop() │ │ │
|
||||
// │ │ │ goroutine │ │ │
|
||||
// │ │ │ │ │ │ │
|
||||
// │ │ │ fsEventsCallback() │ │ │
|
||||
// │ │ │ asm: return to FSEvents │ │ │
|
||||
// │ │ └─────────────────────────────────────────┘ │ │
|
||||
// │ └─────────────────────────────────────────────────────┘ │
|
||||
// └───────────────────────────────────────────────────────────┘
|
||||
//
|
||||
// Goroutines and threading:
|
||||
// - FSEvents delivers the raw C callback on a GCD dispatch queue thread
|
||||
// (an OS thread managed by libdispatch, not a Go goroutine).
|
||||
// - The assembly callback (fsEventsCallbackASM, in the .s files) runs on
|
||||
// that GCD thread in the C calling convention. It never enters Go ABI.
|
||||
// It retains/copies the callback payload and passes it to Go through
|
||||
// eventPipe.
|
||||
// - One Go goroutine per stream chunk (eventLoop, in fsevents_darwin_ffi.go)
|
||||
// blocks on eventFile.Read(), integrated with Go's netpoll so it parks
|
||||
// without consuming an OS thread. When woken by the asm callback, it calls
|
||||
// fsEventsCallback() to classify events and route them to matching
|
||||
// dirWatch event lists.
|
||||
// - subscribe/closeWatch rebuild the stream chunks on the caller's goroutine
|
||||
// under watcherBase.mu. Old streams are swapped out before teardown so a
|
||||
// callback cannot deadlock against stream teardown while routing events.
|
||||
//
|
||||
// Callback delivery:
|
||||
// dirWatch.notify() posts to the shared process-wide debouncer. After a
|
||||
// coalescing window (50 ms min / 500 ms max), the debouncer invokes all
|
||||
// registered WatchCallbacks on its own dedicated goroutine; never on
|
||||
// the GCD thread, the eventLoop goroutine, or the caller's goroutine.
|
||||
// On all backends, events matching a WithIgnore function are filtered
|
||||
// per-subscriber before delivery.
|
||||
//
|
||||
// WatchDirectory flow (caller goroutine):
|
||||
// subscribe/closeWatch snapshots active dirWatches and creates one or more
|
||||
// FSEventStreams with kFSEventStreamEventIdSinceNow. Each stream receives a
|
||||
// chunk of physical watch roots. No directory walk or tree is needed;
|
||||
// FSEvents watches recursively via the kernel, and event classification uses
|
||||
// only the flags.
|
||||
//
|
||||
// Event classification (fsEventsCallback, on eventLoop goroutine):
|
||||
// Each batch delivers arrays of paths, flags, and event IDs. The flags
|
||||
// bitmask may combine multiple states (created + modified + renamed).
|
||||
// Pure removes emit EventDelete with no syscalls. Renames and
|
||||
// remove+create combos do one Lstat to check existence (the kernel
|
||||
// reports some deletions as renames). Everything else emits EventUpdate
|
||||
// with no syscalls.
|
||||
//
|
||||
// Overflow:
|
||||
// flagMustScanSubDirs → ErrOverflow with detail (user/kernel/too-many).
|
||||
//
|
||||
// Root deletion:
|
||||
// Detected in the callback; the logical watch is marked terminated and
|
||||
// receives ErrWatchTerminated. The shared stream remains active for other
|
||||
// watches until the owner closes or reconciles the terminated watch.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ----- FSEvents flag bits (from FSEvents.h) ------------------------------
|
||||
|
||||
const (
|
||||
flagMustScanSubDirs = 0x00000001
|
||||
flagUserDropped = 0x00000002
|
||||
flagKernelDropped = 0x00000004
|
||||
flagHistoryDone = 0x00000010
|
||||
|
||||
flagItemCreated = 0x00000100
|
||||
flagItemRemoved = 0x00000200
|
||||
flagItemInodeMetaMod = 0x00000400
|
||||
flagItemRenamed = 0x00000800
|
||||
flagItemModified = 0x00001000
|
||||
flagItemFinderInfoMod = 0x00002000
|
||||
flagItemChangeOwner = 0x00004000
|
||||
flagItemXattrMod = 0x00008000
|
||||
flagItemIsFile = 0x00010000
|
||||
flagItemIsDir = 0x00020000
|
||||
flagItemIsSymlink = 0x00040000
|
||||
flagItemIsHardlink = 0x00100000
|
||||
flagItemIsLastHardlink = 0x00200000
|
||||
flagItemCloned = 0x00400000
|
||||
|
||||
// kFSEventStreamCreateFlagUseCFTypes (0x1) |
|
||||
// kFSEventStreamCreateFlagFileEvents (0x10) is hardcoded in the
|
||||
// arch-specific assembly trampolines (fsevents_darwin_ffi_{arm64,amd64}.s).
|
||||
|
||||
cfStringEncodingUTF8 = 0x08000100
|
||||
|
||||
// kFSEventStreamEventIdSinceNow == ((FSEventStreamEventId)0xFFFFFFFFFFFFFFFFULL)
|
||||
eventIDSinceNow = uint64(0xFFFFFFFFFFFFFFFF)
|
||||
)
|
||||
|
||||
const ignoredFlags = flagItemIsHardlink | flagItemIsLastHardlink |
|
||||
flagItemIsSymlink | flagItemIsDir | flagItemIsFile | flagItemCloned
|
||||
|
||||
// fsEventStreamContext mirrors the C struct of the same name.
|
||||
//
|
||||
// typedef struct {
|
||||
// CFIndex version; // signed long, 8 bytes on 64-bit
|
||||
// void *info; // pointer
|
||||
// void *retain; // pointer
|
||||
// void *release; // pointer
|
||||
// void *copyDescription; // pointer
|
||||
// } FSEventStreamContext;
|
||||
type fsEventStreamContext struct {
|
||||
version int
|
||||
info uintptr
|
||||
retain uintptr
|
||||
release uintptr
|
||||
copyDescription uintptr
|
||||
}
|
||||
|
||||
type fseventsState struct {
|
||||
terminated atomic.Bool
|
||||
}
|
||||
|
||||
type fseventsStream struct {
|
||||
stream atomic.Uintptr
|
||||
cb *streamCallback
|
||||
pinner runtime.Pinner
|
||||
}
|
||||
|
||||
// ----- the watcherImpl -------------------------------------------------------
|
||||
|
||||
// fsEventsBackend.
|
||||
type fsEventsBackend struct {
|
||||
watcherBase
|
||||
|
||||
mu sync.Mutex
|
||||
watches map[*dirWatch]*fseventsState
|
||||
streams []*fseventsStream
|
||||
}
|
||||
|
||||
func init() {
|
||||
fseventsWatcher.factory = func() watcherImpl { return newFSEventsBackend() }
|
||||
fseventsWatcher.sequence = fsEventsGetCurrentEventID
|
||||
}
|
||||
|
||||
func newFSEventsBackend() *fsEventsBackend {
|
||||
b := &fsEventsBackend{
|
||||
watches: make(map[*dirWatch]*fseventsState),
|
||||
}
|
||||
b.watcherBase.init(b)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *fsEventsBackend) start() error {
|
||||
b.notifyStarted()
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkWatcher mirrors the helper of the same name.
|
||||
func checkWatcher(w *dirWatch) error {
|
||||
info, err := os.Stat(w.physicalDir)
|
||||
if err != nil {
|
||||
return &dirWatchError{err: err, dirWatch: w}
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return &dirWatchError{err: syscall.ENOTDIR, dirWatch: w}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
errCFStringCreateNull = errors.New("CFStringCreate returned NULL")
|
||||
errCFArrayCreateNull = errors.New("CFArrayCreate returned NULL")
|
||||
errStreamCreateNull = errors.New("FSEventStreamCreate returned NULL")
|
||||
errStreamStartFailed = errors.New("error starting FSEvents stream")
|
||||
)
|
||||
|
||||
var (
|
||||
errFSEventsUserDropped = fmt.Errorf("events were dropped by the FSEvents client: %w", ErrOverflow)
|
||||
errFSEventsKernelDropped = fmt.Errorf("events were dropped by the kernel: %w", ErrOverflow)
|
||||
errFSEventsTooMany = fmt.Errorf("too many events: %w", ErrOverflow)
|
||||
)
|
||||
|
||||
const fseventsPathsPerStream = 512
|
||||
|
||||
type fseventsWatchSnapshot struct {
|
||||
w *dirWatch
|
||||
state *fseventsState
|
||||
}
|
||||
|
||||
func (b *fsEventsBackend) activeWatchesLocked() []fseventsWatchSnapshot {
|
||||
watches := make([]fseventsWatchSnapshot, 0, len(b.watches))
|
||||
for w, state := range b.watches {
|
||||
if state.terminated.Load() {
|
||||
continue
|
||||
}
|
||||
watches = append(watches, fseventsWatchSnapshot{w: w, state: state})
|
||||
}
|
||||
return watches
|
||||
}
|
||||
|
||||
func (b *fsEventsBackend) startStreams(watches []fseventsWatchSnapshot) ([]*fseventsStream, error) {
|
||||
return startFSEventsStreams(watches, b.startStream)
|
||||
}
|
||||
|
||||
func startFSEventsStreams(watches []fseventsWatchSnapshot, startStream func([]string, []fseventsWatchSnapshot) (*fseventsStream, error)) ([]*fseventsStream, error) {
|
||||
if len(watches) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(watches))
|
||||
paths := make([]string, 0, len(watches))
|
||||
for _, watch := range watches {
|
||||
path := watch.w.physicalDir
|
||||
if _, ok := seen[path]; ok {
|
||||
continue
|
||||
}
|
||||
seen[path] = struct{}{}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
|
||||
stream, err := startStream(paths, watches)
|
||||
if err == nil {
|
||||
return []*fseventsStream{stream}, nil
|
||||
}
|
||||
|
||||
streams := make([]*fseventsStream, 0, (len(paths)+fseventsPathsPerStream-1)/fseventsPathsPerStream)
|
||||
remainingPaths := paths
|
||||
for len(remainingPaths) > 0 {
|
||||
chunkLen := min(len(remainingPaths), fseventsPathsPerStream)
|
||||
chunkPaths := remainingPaths[:chunkLen]
|
||||
stream, err := startStream(chunkPaths, watchesForFSEventsPaths(watches, chunkPaths))
|
||||
if err != nil {
|
||||
stopFSEventsStreams(streams)
|
||||
return nil, err
|
||||
}
|
||||
streams = append(streams, stream)
|
||||
remainingPaths = remainingPaths[chunkLen:]
|
||||
}
|
||||
return streams, nil
|
||||
}
|
||||
|
||||
func watchesForFSEventsPaths(watches []fseventsWatchSnapshot, paths []string) []fseventsWatchSnapshot {
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
filtered := make([]fseventsWatchSnapshot, 0, len(watches))
|
||||
for _, watch := range watches {
|
||||
if _, ok := slices.BinarySearch(paths, watch.w.physicalDir); ok {
|
||||
filtered = append(filtered, watch)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// startStream creates and starts one FSEventStream watching all supplied paths.
|
||||
func (b *fsEventsBackend) startStream(paths []string, watches []fseventsWatchSnapshot) (*fseventsStream, error) {
|
||||
if len(paths) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cfStrings := make([]uintptr, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
dirCStr := append([]byte(path), 0)
|
||||
cfDir := cfStringCreate(0, unsafe.Pointer(&dirCStr[0]), cfStringEncodingUTF8)
|
||||
if cfDir == 0 {
|
||||
for _, cfString := range cfStrings {
|
||||
cfRelease(cfString)
|
||||
}
|
||||
return nil, errCFStringCreateNull
|
||||
}
|
||||
cfStrings = append(cfStrings, cfDir)
|
||||
}
|
||||
defer func() {
|
||||
for _, cfString := range cfStrings {
|
||||
cfRelease(cfString)
|
||||
}
|
||||
}()
|
||||
|
||||
pathsToWatch := cfArrayCreate(0, unsafe.Pointer(&cfStrings[0]), len(cfStrings), 0)
|
||||
if pathsToWatch == 0 {
|
||||
return nil, errCFArrayCreateNull
|
||||
}
|
||||
defer cfRelease(pathsToWatch)
|
||||
|
||||
cb, err := newStreamCallback(watches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := &fseventsStream{cb: cb}
|
||||
state.pinner.Pin(cb)
|
||||
|
||||
ctx := fsEventStreamContext{info: uintptr(unsafe.Pointer(cb))}
|
||||
|
||||
stream := fsEventStreamCreate(
|
||||
0,
|
||||
fsEventsCallbackAsmAddr,
|
||||
unsafe.Pointer(&ctx),
|
||||
pathsToWatch,
|
||||
eventIDSinceNow,
|
||||
0.001,
|
||||
)
|
||||
if stream == 0 {
|
||||
cb.close()
|
||||
state.cb = nil
|
||||
state.pinner.Unpin()
|
||||
return nil, errStreamCreateNull
|
||||
}
|
||||
|
||||
fsEventStreamSetDispatchQueue(stream, cb.queue)
|
||||
if fsEventStreamStart(stream) == 0 {
|
||||
fsEventStreamInvalidate(stream)
|
||||
fsEventStreamRelease(stream)
|
||||
cb.close()
|
||||
state.cb = nil
|
||||
state.pinner.Unpin()
|
||||
return nil, errStreamStartFailed
|
||||
}
|
||||
fsEventStreamFlushSync(stream)
|
||||
state.stream.Store(stream)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// teardownStream performs the full FSEventStream cleanup. Stop and Invalidate
|
||||
// prevent new callbacks, waitDispatchQueue waits for callbacks already queued
|
||||
// on the stream's serial dispatch queue, and cb.close joins the Go event loop
|
||||
// after it drains payloads already written to the pipe.
|
||||
func teardownStream(stream uintptr, cb *streamCallback) {
|
||||
fsEventStreamStop(stream)
|
||||
if cb != nil {
|
||||
fsEventStreamInvalidate(stream)
|
||||
cb.waitDispatchQueue()
|
||||
cb.close()
|
||||
} else {
|
||||
fsEventStreamInvalidate(stream)
|
||||
}
|
||||
fsEventStreamRelease(stream)
|
||||
}
|
||||
|
||||
// The atomic Swap gates teardown so concurrent or repeated calls are safe:
|
||||
// only the goroutine that observes a non-zero stream performs the cleanup.
|
||||
func stopFSEventsStreams(streams []*fseventsStream) {
|
||||
for _, stream := range streams {
|
||||
stopFSEventsStream(stream)
|
||||
}
|
||||
}
|
||||
|
||||
func stopFSEventsStream(state *fseventsStream) {
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
stream := state.stream.Swap(0)
|
||||
if stream == 0 {
|
||||
return
|
||||
}
|
||||
cb := state.cb
|
||||
teardownStream(stream, cb)
|
||||
state.cb = nil
|
||||
state.pinner.Unpin()
|
||||
}
|
||||
|
||||
// subscribe mirrors `fsEventsBackend::subscribe`.
|
||||
func (b *fsEventsBackend) subscribe(w *dirWatch) error {
|
||||
return b.subscribeMany([]*dirWatch{w})
|
||||
}
|
||||
|
||||
func (b *fsEventsBackend) subscribeMany(watchesToAdd []*dirWatch) error {
|
||||
if len(watchesToAdd) == 0 {
|
||||
return nil
|
||||
}
|
||||
states := make(map[*dirWatch]*fseventsState, len(watchesToAdd))
|
||||
for _, w := range watchesToAdd {
|
||||
if err := checkWatcher(w); err != nil {
|
||||
return err
|
||||
}
|
||||
states[w] = &fseventsState{}
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
for w, state := range states {
|
||||
w.state = state
|
||||
b.watches[w] = state
|
||||
}
|
||||
watches := b.activeWatchesLocked()
|
||||
b.mu.Unlock()
|
||||
|
||||
streams, err := b.startStreams(watches)
|
||||
if err != nil {
|
||||
b.mu.Lock()
|
||||
for w, state := range states {
|
||||
if b.watches[w] == state {
|
||||
delete(b.watches, w)
|
||||
w.state = nil
|
||||
}
|
||||
}
|
||||
b.mu.Unlock()
|
||||
return &dirWatchError{err: err, dirWatch: watchesToAdd[0]}
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
oldStreams := b.streams
|
||||
b.streams = streams
|
||||
b.mu.Unlock()
|
||||
stopFSEventsStreams(oldStreams)
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeWatch mirrors `fsEventsBackend::closeWatch`.
|
||||
func (b *fsEventsBackend) closeWatch(w *dirWatch) error {
|
||||
state, _ := w.state.(*fseventsState)
|
||||
w.state = nil
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
state.terminated.Store(true)
|
||||
|
||||
b.mu.Lock()
|
||||
delete(b.watches, w)
|
||||
watches := b.activeWatchesLocked()
|
||||
b.mu.Unlock()
|
||||
|
||||
streams, err := b.startStreams(watches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
oldStreams := b.streams
|
||||
b.streams = streams
|
||||
b.mu.Unlock()
|
||||
stopFSEventsStreams(oldStreams)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fsEventsCallback processes a batch of FSEvents. The payload contains callback
|
||||
// data retained/copied by the assembly before it returned control to Go.
|
||||
//
|
||||
// Called by streamCallback.eventLoop on a per-stream Go goroutine (not the
|
||||
// dispatch queue thread). The C callback assembly signals the event loop via a
|
||||
// pipe; see fsevents_darwin_ffi.go.
|
||||
func fsEventsCallback(cb *streamCallback, payload *fsEventsCallbackPayload) {
|
||||
defer payload.close()
|
||||
|
||||
const (
|
||||
flagSize = unsafe.Sizeof(uint32(0))
|
||||
idSize = unsafe.Sizeof(uint64(0))
|
||||
)
|
||||
|
||||
if payload == nil || payload.paths == 0 || payload.flags == 0 || payload.ids == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
numEvents := payload.numEvents
|
||||
paths := payload.paths
|
||||
flags := payload.flags
|
||||
ids := payload.ids
|
||||
|
||||
watches := cb.watches
|
||||
touched := map[*dirWatch]struct{}{}
|
||||
|
||||
for i := range numEvents {
|
||||
flag := *(*uint32)(unsafe.Add(nil, flags+i*flagSize))
|
||||
eventID := *(*uint64)(unsafe.Add(nil, ids+i*idSize))
|
||||
pathRef := cfArrayGetValueAtIndex(paths, int(i))
|
||||
path := cfStringToNFC(pathRef)
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
isRemoved := flag&flagItemRemoved != 0
|
||||
isRenamed := flag&flagItemRenamed != 0
|
||||
isCreated := flag&flagItemCreated != 0
|
||||
isDone := flag&flagHistoryDone != 0
|
||||
|
||||
if flag&flagMustScanSubDirs != 0 {
|
||||
var overflow error
|
||||
switch {
|
||||
case flag&flagUserDropped != 0:
|
||||
overflow = errFSEventsUserDropped
|
||||
case flag&flagKernelDropped != 0:
|
||||
overflow = errFSEventsKernelDropped
|
||||
default:
|
||||
overflow = errFSEventsTooMany
|
||||
}
|
||||
for _, watch := range watches {
|
||||
if watch.state.terminated.Load() {
|
||||
continue
|
||||
}
|
||||
if fseventsOverflowMatches(watch.w, path) {
|
||||
watch.w.events.setError(overflow)
|
||||
touched[watch.w] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isDone {
|
||||
break
|
||||
}
|
||||
|
||||
if flag&^uint32(ignoredFlags) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
rawPath := path
|
||||
pathExists := false
|
||||
pathExistsKnown := false
|
||||
|
||||
for _, watch := range watches {
|
||||
if watch.state.terminated.Load() {
|
||||
continue
|
||||
}
|
||||
w := watch.w
|
||||
displayPath, ok := fseventsDisplayPath(w, rawPath)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip events for the watched directory itself unless it's been
|
||||
// removed. fseventsd reports a change on the watched dir when a
|
||||
// child is added or removed; subscribers observe changes *within*
|
||||
// the directory, not the dir's own metadata churn.
|
||||
// (A removal of the dir is still propagated because Watcher
|
||||
// relies on it to tear down the stream.)
|
||||
if displayPath == w.dir && !isRemoved && !isRenamed {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case isRemoved && !isCreated:
|
||||
if displayPath == w.dir {
|
||||
w.events.removeWatchRootAt(displayPath, eventID)
|
||||
} else {
|
||||
w.events.removeAt(displayPath, eventID)
|
||||
}
|
||||
if w.terminateCallbacksForDeletedRoot(displayPath, eventID, fmt.Errorf("%w: watched directory removed", ErrWatchTerminated)) {
|
||||
touched[w] = struct{}{}
|
||||
}
|
||||
if displayPath == w.dir {
|
||||
watch.state.terminated.Store(true)
|
||||
w.events.setError(fmt.Errorf("%w: watched directory removed", ErrWatchTerminated))
|
||||
}
|
||||
case isRenamed || (isRemoved && isCreated):
|
||||
if !pathExistsKnown {
|
||||
var st unix.Stat_t
|
||||
pathExists = unix.Lstat(rawPath, &st) == nil
|
||||
pathExistsKnown = true
|
||||
}
|
||||
if pathExists {
|
||||
if displayPath == w.dir {
|
||||
w.events.updateWatchRootAt(displayPath, eventID)
|
||||
} else {
|
||||
w.events.updateAt(displayPath, eventID)
|
||||
}
|
||||
} else {
|
||||
if displayPath == w.dir {
|
||||
w.events.removeWatchRootAt(displayPath, eventID)
|
||||
} else {
|
||||
w.events.removeAt(displayPath, eventID)
|
||||
}
|
||||
if w.terminateCallbacksForDeletedRoot(displayPath, eventID, fmt.Errorf("%w: watched directory removed", ErrWatchTerminated)) {
|
||||
touched[w] = struct{}{}
|
||||
}
|
||||
if displayPath == w.dir {
|
||||
watch.state.terminated.Store(true)
|
||||
w.events.setError(fmt.Errorf("%w: watched directory removed", ErrWatchTerminated))
|
||||
}
|
||||
}
|
||||
default:
|
||||
if displayPath == w.dir {
|
||||
w.events.updateWatchRootAt(displayPath, eventID)
|
||||
} else {
|
||||
w.events.updateAt(displayPath, eventID)
|
||||
}
|
||||
}
|
||||
touched[w] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for w := range touched {
|
||||
w.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func fseventsDisplayPath(w *dirWatch, rawPath string) (string, bool) {
|
||||
if isInDirectoryOrSelf(w.physicalDir, rawPath) {
|
||||
return w.displayPath(rawPath), true
|
||||
}
|
||||
if w.physicalDir != w.dir && isInDirectoryOrSelf(w.dir, rawPath) {
|
||||
return rawPath, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func fseventsOverflowMatches(w *dirWatch, rawPath string) bool {
|
||||
if isInDirectoryOrSelf(w.physicalDir, rawPath) || isInDirectoryOrSelf(rawPath, w.physicalDir) {
|
||||
return true
|
||||
}
|
||||
return w.physicalDir != w.dir && (isInDirectoryOrSelf(w.dir, rawPath) || isInDirectoryOrSelf(rawPath, w.dir))
|
||||
}
|
||||
Reference in New Issue
Block a user