272 lines
6.3 KiB
Go
272 lines
6.3 KiB
Go
package fswatch
|
|
|
|
import "sync"
|
|
|
|
// EventKind classifies a filesystem change.
|
|
type EventKind int
|
|
|
|
const (
|
|
EventUpdate EventKind = iota + 1
|
|
EventDelete
|
|
)
|
|
|
|
func (k EventKind) String() string {
|
|
switch k {
|
|
case EventUpdate:
|
|
return "update"
|
|
case EventDelete:
|
|
return "delete"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// Event describes a single filesystem change.
|
|
type Event struct {
|
|
Kind EventKind
|
|
Path string
|
|
includedWatchRoot bool
|
|
}
|
|
|
|
// eventEntry tracks coalescing state during a debounce batch.
|
|
type eventEntry struct {
|
|
createdSeq uint64
|
|
updatedSeq uint64
|
|
deletedSeq uint64
|
|
includedWatchRoot bool
|
|
}
|
|
|
|
// eventList coalesces filesystem events by path within a debounce window.
|
|
// - create after delete → update (rapid delete+recreate)
|
|
// - getEvents skips entries that were both created and deleted
|
|
type eventList struct {
|
|
mu sync.Mutex
|
|
entries map[string]*eventEntry
|
|
err error
|
|
seq uint64
|
|
}
|
|
|
|
// create records a new-file event for path. Both create and update
|
|
// produce EventUpdate externally; sequence state tracks coalescing
|
|
// (create+delete within a batch cancels out).
|
|
func (el *eventList) create(path string) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
seq := el.nextSeqLocked()
|
|
el.createLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) createAt(path string, seq uint64) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
el.advanceSeqLocked(seq)
|
|
el.createLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) createLocked(path string, seq uint64) {
|
|
entry := el.getOrCreate(path)
|
|
if entry.isDeleted() {
|
|
// Rapid delete+recreate: clear both flags so the entry
|
|
// emits EventUpdate (the default for non-deleted entries).
|
|
// https://github.com/parcel-bundler/watcher/issues/72
|
|
entry.deletedSeq = 0
|
|
entry.createdSeq = 0
|
|
entry.updatedSeq = seq
|
|
} else {
|
|
entry.createdSeq = seq
|
|
}
|
|
}
|
|
|
|
// update records an update event for path.
|
|
func (el *eventList) update(path string) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
seq := el.nextSeqLocked()
|
|
el.updateLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) updateAt(path string, seq uint64) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
el.advanceSeqLocked(seq)
|
|
el.updateLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) updateWatchRootAt(path string, seq uint64) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
el.advanceSeqLocked(seq)
|
|
el.updateLocked(path, seq)
|
|
el.getOrCreate(path).includedWatchRoot = true
|
|
}
|
|
|
|
func (el *eventList) updateLocked(path string, seq uint64) {
|
|
el.getOrCreate(path).updatedSeq = seq
|
|
}
|
|
|
|
// remove records a delete event for path.
|
|
func (el *eventList) remove(path string) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
seq := el.nextSeqLocked()
|
|
el.removeLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) removeAndGetSequence(path string) uint64 {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
seq := el.nextSeqLocked()
|
|
el.removeLocked(path, seq)
|
|
return seq
|
|
}
|
|
|
|
func (el *eventList) removeAt(path string, seq uint64) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
el.advanceSeqLocked(seq)
|
|
el.removeLocked(path, seq)
|
|
}
|
|
|
|
func (el *eventList) removeWatchRootAt(path string, seq uint64) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
el.advanceSeqLocked(seq)
|
|
el.removeLocked(path, seq)
|
|
el.getOrCreate(path).includedWatchRoot = true
|
|
}
|
|
|
|
func (el *eventList) removeLocked(path string, seq uint64) {
|
|
entry := el.getOrCreate(path)
|
|
entry.deletedSeq = seq
|
|
}
|
|
|
|
// size returns the number of tracked entries (including ones that may
|
|
// cancel out in getEvents).
|
|
func (el *eventList) size() int {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
return len(el.entries)
|
|
}
|
|
|
|
// snapshotLocked returns the current set of pending events with
|
|
// create+delete pairs filtered out. Caller must hold el.mu.
|
|
func (el *eventList) snapshotLocked() []Event {
|
|
return el.snapshotSinceLocked(0)
|
|
}
|
|
|
|
func (el *eventList) snapshotSinceLocked(startSeq uint64) []Event {
|
|
out := make([]Event, 0, len(el.entries))
|
|
for path, e := range el.entries {
|
|
kind, ok := e.kindSince(startSeq)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = append(out, Event{Kind: kind, Path: path, includedWatchRoot: e.includedWatchRoot})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// getEvents returns a snapshot of events, skipping entries that were both
|
|
// created and deleted. Order is not guaranteed.
|
|
func (el *eventList) getEvents() []Event {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
return el.snapshotLocked()
|
|
}
|
|
|
|
// drain atomically snapshots all pending events and the stored error,
|
|
// then clears the list. This prevents events added between a separate
|
|
// getEvents+clear from being silently dropped.
|
|
func (el *eventList) drain() ([]Event, error) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
out := el.snapshotLocked()
|
|
err := el.err
|
|
el.entries = nil
|
|
el.err = nil
|
|
return out, err
|
|
}
|
|
|
|
func (el *eventList) drainForSequences(startSeqs []uint64) ([][]Event, error) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
out := make([][]Event, len(startSeqs))
|
|
for i, startSeq := range startSeqs {
|
|
out[i] = el.snapshotSinceLocked(startSeq)
|
|
}
|
|
err := el.err
|
|
el.entries = nil
|
|
el.err = nil
|
|
return out, err
|
|
}
|
|
|
|
// setError stores the first error encountered (later errors are ignored).
|
|
func (el *eventList) setError(err error) {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
if el.err == nil {
|
|
el.err = err
|
|
}
|
|
}
|
|
|
|
// hasError reports whether an error has been recorded.
|
|
func (el *eventList) hasError() bool {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
return el.err != nil
|
|
}
|
|
|
|
// getError returns the stored error (or nil if none).
|
|
func (el *eventList) getError() error {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
return el.err
|
|
}
|
|
|
|
func (el *eventList) getOrCreate(path string) *eventEntry {
|
|
if el.entries == nil {
|
|
el.entries = make(map[string]*eventEntry)
|
|
}
|
|
if e, ok := el.entries[path]; ok {
|
|
return e
|
|
}
|
|
e := &eventEntry{}
|
|
el.entries[path] = e
|
|
return e
|
|
}
|
|
|
|
func (el *eventList) sequence() uint64 {
|
|
el.mu.Lock()
|
|
defer el.mu.Unlock()
|
|
return el.seq
|
|
}
|
|
|
|
func (el *eventList) nextSeqLocked() uint64 {
|
|
el.seq++
|
|
return el.seq
|
|
}
|
|
|
|
func (el *eventList) advanceSeqLocked(seq uint64) {
|
|
if seq > el.seq {
|
|
el.seq = seq
|
|
}
|
|
}
|
|
|
|
func (e *eventEntry) isDeleted() bool {
|
|
return e.deletedSeq > e.createdSeq && e.deletedSeq > e.updatedSeq
|
|
}
|
|
|
|
func (e *eventEntry) kindSince(startSeq uint64) (EventKind, bool) {
|
|
if e.deletedSeq > startSeq {
|
|
if e.createdSeq > startSeq && e.createdSeq < e.deletedSeq && e.updatedSeq < e.deletedSeq {
|
|
return 0, false
|
|
}
|
|
return EventDelete, true
|
|
}
|
|
seq := max(e.createdSeq, e.updatedSeq)
|
|
if seq > startSeq {
|
|
return EventUpdate, true
|
|
}
|
|
return 0, false
|
|
}
|