import "fmt"
+const (
+ FSN_CREATE = 1
+ FSN_MODIFY = 2
+ FSN_DELETE = 4
+ FSN_RENAME = 8
+
+ FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE
+)
+
+// Purge events from interal chan to external chan if passes filter
+func (w *Watcher) purgeEvents() {
+ for ev := range w.internalEvent {
+ sendEvent := false
+ fsnFlags := w.fsnFlags[ev.Name]
+
+ if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() {
+ sendEvent = true
+ }
+
+ if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() {
+ sendEvent = true
+ }
+
+ if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() {
+ sendEvent = true
+ }
+
+ if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() {
+ sendEvent = true
+ }
+
+ if sendEvent {
+ w.Event <- ev
+ }
+ }
+
+ close(w.Event)
+}
+
+func (w *Watcher) Watch(path string) error {
+ w.fsnFlags[path] = FSN_ALL
+ return w.watch(path)
+}
+
+func (w *Watcher) WatchFlags(path string, flags uint32) error {
+ w.fsnFlags[path] = flags
+ return w.watch(path)
+}
+
+func (w *Watcher) RemoveWatch(path string) error {
+ delete(w.fsnFlags, path)
+ return w.removeWatch(path)
+}
+
// String formats the event e in the form
// "filename: DELETE|MODIFY|..."
func (e *FileEvent) String() string {
func (e *FileEvent) IsRename() bool { return (e.mask & NOTE_RENAME) == NOTE_RENAME }
type Watcher struct {
- kq int // File descriptor (as returned by the kqueue() syscall)
- watches map[string]int // Map of watched file diescriptors (key: path)
- paths map[int]string // Map of watched paths (key: watch descriptor)
- finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
- Error chan error // Errors are sent on this channel
- Event chan *FileEvent // Events are returned on this channel
- done chan bool // Channel for sending a "quit message" to the reader goroutine
- isClosed bool // Set to true when Close() is first called
- kbuf [1]syscall.Kevent_t // An event buffer for Add/Remove watch
+ kq int // File descriptor (as returned by the kqueue() syscall)
+ watches map[string]int // Map of watched file diescriptors (key: path)
+ fsnFlags map[string]uint32 // Map of watched files to flags used for filter
+ paths map[int]string // Map of watched paths (key: watch descriptor)
+ finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
+ Error chan error // Errors are sent on this channel
+ internalEvent chan *FileEvent // Events are queued on this channel
+ Event chan *FileEvent // Events are returned on this channel
+ done chan bool // Channel for sending a "quit message" to the reader goroutine
+ isClosed bool // Set to true when Close() is first called
+ kbuf [1]syscall.Kevent_t // An event buffer for Add/Remove watch
}
// NewWatcher creates and returns a new kevent instance using kqueue(2)
return nil, os.NewSyscallError("kqueue", errno)
}
w := &Watcher{
- kq: fd,
- watches: make(map[string]int),
- paths: make(map[int]string),
- finfo: make(map[int]os.FileInfo),
- Event: make(chan *FileEvent),
- Error: make(chan error),
- done: make(chan bool, 1),
+ kq: fd,
+ watches: make(map[string]int),
+ fsnFlags: make(map[string]uint32),
+ paths: make(map[int]string),
+ finfo: make(map[int]os.FileInfo),
+ internalEvent: make(chan *FileEvent),
+ Event: make(chan *FileEvent),
+ Error: make(chan error),
+ done: make(chan bool, 1),
}
go w.readEvents()
+ go w.purgeEvents()
return w, nil
}
// Send "quit" message to the reader goroutine
w.done <- true
for path := range w.watches {
- w.RemoveWatch(path)
+ w.removeWatch(path)
}
return nil
}
// Watch adds path to the watched file set, watching all events.
-func (w *Watcher) Watch(path string) error {
+func (w *Watcher) watch(path string) error {
return w.addWatch(path, NOTE_ALLEVENTS)
}
// RemoveWatch removes path from the watched file set.
-func (w *Watcher) RemoveWatch(path string) error {
+func (w *Watcher) removeWatch(path string) error {
watchfd, ok := w.watches[path]
if !ok {
return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
if errno != nil {
w.Error <- os.NewSyscallError("close", errno)
}
- close(w.Event)
+ close(w.internalEvent)
close(w.Error)
return
}
w.sendDirectoryChangeEvents(fileEvent.Name)
} else {
// Send the event on the events channel
- w.Event <- fileEvent
+ w.internalEvent <- fileEvent
}
// Move to next event
filePath := filepath.Join(dirPath, fileInfo.Name())
// Watch file to mimic linux fsnotify
e := w.addWatch(filePath, NOTE_DELETE|NOTE_WRITE|NOTE_RENAME)
+ w.fsnFlags[filePath] = FSN_ALL
if e != nil {
return e
}
if fileInfo.IsDir() == false {
filePath := filepath.Join(dirPath, fileInfo.Name())
if w.watches[filePath] == 0 {
+ w.fsnFlags[filePath] = FSN_ALL
// Send create event
fileEvent := new(FileEvent)
fileEvent.Name = filePath
fileEvent.create = true
- w.Event <- fileEvent
+ w.internalEvent <- fileEvent
}
}
}