fsevents.go (112 lines of code) (raw):

// +build darwin // Package fsevents provides file system notifications on macOS. package fsevents import ( "sync" "syscall" "time" ) // CreateFlags for creating a New stream. type CreateFlags uint32 // kFSEventStreamCreateFlag... const ( // use CoreFoundation types instead of raw C types (disabled) useCFTypes CreateFlags = 1 << iota // NoDefer sends events on the leading edge (for interactive applications). // By default events are delivered after latency seconds (for background tasks). NoDefer // WatchRoot for a change to occur to a directory along the path being watched. WatchRoot // IgnoreSelf doesn't send events triggered by the current process (macOS 10.6+). IgnoreSelf // FileEvents sends events about individual files, generating significantly // more events (macOS 10.7+) than directory level notifications. FileEvents ) // EventFlags passed to the FSEventStreamCallback function. type EventFlags uint32 // kFSEventStreamEventFlag... const ( // MustScanSubDirs indicates that events were coalesced hierarchically. MustScanSubDirs EventFlags = 1 << iota // UserDropped or KernelDropped is set alongside MustScanSubDirs // to help diagnose the problem. UserDropped KernelDropped // EventIDsWrapped indicates the 64-bit event ID counter wrapped around. EventIDsWrapped // HistoryDone is a sentinel event when retrieving events sinceWhen. HistoryDone // RootChanged indicates a change to a directory along the path being watched. RootChanged // Mount for a volume mounted underneath the path being monitored. Mount // Unmount event occurs after a volume is unmounted. Unmount // The following flags are only set when using FileEvents. ItemCreated ItemRemoved ItemInodeMetaMod ItemRenamed ItemModified ItemFinderInfoMod ItemChangeOwner ItemXattrMod ItemIsFile ItemIsDir ItemIsSymlink ) // Event represents a single file system notification. type Event struct { Path string Flags EventFlags ID uint64 } // DeviceForPath returns the device ID for the specified volume. func DeviceForPath(path string) (int32, error) { stat := syscall.Stat_t{} if err := syscall.Lstat(path, &stat); err != nil { return 0, err } return stat.Dev, nil } // EventStream is the primary interface to FSEvents // You can provide your own event channel if you wish (or one will be // created on Start). // // es := &EventStream{Paths: []string{"/tmp"}, Flags: 0} // es.Start() // es.Stop() // ... type EventStream struct { stream FSEventStreamRef rlref CFRunLoopRef hasFinalizer bool registryID uintptr uuid string Events chan []Event Paths []string Flags CreateFlags EventID uint64 Resume bool Latency time.Duration // syscall represents this with an int32 Device int32 } // eventStreamRegistry is a lookup table for EventStream references passed to // cgo. In Go 1.6+ passing a Go pointer to a Go pointer to cgo is not allowed. // To get around this issue, we pass only an integer. type eventStreamRegistry struct { sync.Mutex m map[uintptr]*EventStream lastID uintptr } var registry = eventStreamRegistry{m: map[uintptr]*EventStream{}} func (r *eventStreamRegistry) Add(e *EventStream) uintptr { r.Lock() defer r.Unlock() r.lastID++ r.m[r.lastID] = e return r.lastID } func (r *eventStreamRegistry) Get(i uintptr) *EventStream { r.Lock() defer r.Unlock() return r.m[i] } func (r *eventStreamRegistry) Delete(i uintptr) { r.Lock() defer r.Unlock() delete(r.m, i) } // Start listening to an event stream. func (es *EventStream) Start() { if es.Events == nil { es.Events = make(chan []Event) } // register eventstream in the local registry for later lookup // in C callback cbInfo := registry.Add(es) es.registryID = cbInfo if es.Device != 0 { es.uuid = GetDeviceUUID(es.Device) } es.start(es.Paths, cbInfo) } // Flush events that have occurred but haven't been delivered. func (es *EventStream) Flush(sync bool) { flush(es.stream, sync) } // Stop listening to the event stream. func (es *EventStream) Stop() { if es.stream != nil { stop(es.stream, es.rlref) es.stream = nil } // Remove eventstream from the registry registry.Delete(es.registryID) es.registryID = 0 } // Restart listening. func (es *EventStream) Restart() { es.Stop() es.Resume = true es.Start() }