compose/pkg/watch/watcher_naive.go

212 lines
4.4 KiB
Go
Raw Normal View History

// +build !darwin
2018-08-16 20:53:47 +02:00
package watch
import (
"fmt"
2018-08-16 20:53:47 +02:00
"log"
"os"
"path/filepath"
"sync"
2018-08-16 20:53:47 +02:00
"github.com/pkg/errors"
2018-08-16 20:53:47 +02:00
"github.com/windmilleng/fsnotify"
"github.com/windmilleng/tilt/internal/ospath"
2018-08-16 20:53:47 +02:00
)
// A naive file watcher that uses the plain fsnotify API.
// Used on all non-Darwin systems (including Windows & Linux).
//
// All OS-specific codepaths are handled by fsnotify.
type naiveNotify struct {
2018-08-16 20:53:47 +02:00
watcher *fsnotify.Watcher
events chan fsnotify.Event
wrappedEvents chan FileEvent
2018-08-16 20:53:47 +02:00
errors chan error
mu sync.Mutex
// Paths that we're watching that should be passed up to the caller.
// Note that we may have to watch ancestors of these paths
// in order to fulfill the API promise.
notifyList map[string]bool
2018-08-16 20:53:47 +02:00
}
func (d *naiveNotify) Add(name string) error {
2018-08-16 20:53:47 +02:00
fi, err := os.Stat(name)
if err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "notify.Add(%q)", name)
2018-08-16 20:53:47 +02:00
}
2018-09-14 23:13:36 +02:00
// if it's a file that doesn't exist, watch its parent
2018-08-16 20:53:47 +02:00
if os.IsNotExist(err) {
err = d.watchAncestorOfMissingPath(name)
2018-08-16 20:53:47 +02:00
if err != nil {
return errors.Wrapf(err, "watchAncestorOfMissingPath(%q)", name)
2018-08-16 20:53:47 +02:00
}
} else if fi.IsDir() {
err = d.watchRecursively(name)
if err != nil {
return errors.Wrapf(err, "notify.Add(%q)", name)
2018-08-16 20:53:47 +02:00
}
} else {
err = d.watcher.Add(filepath.Dir(name))
2018-08-16 20:53:47 +02:00
if err != nil {
return errors.Wrapf(err, "notify.Add(%q)", filepath.Dir(name))
2018-08-16 20:53:47 +02:00
}
}
d.mu.Lock()
defer d.mu.Unlock()
d.notifyList[name] = true
2018-08-16 20:53:47 +02:00
return nil
}
func (d *naiveNotify) watchRecursively(dir string) error {
2018-08-16 20:53:47 +02:00
return filepath.Walk(dir, func(path string, mode os.FileInfo, err error) error {
if err != nil {
return err
}
if !mode.IsDir() {
return nil
}
2018-09-14 23:13:36 +02:00
err = d.watcher.Add(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.Wrapf(err, "watcher.Add(%q)", path)
2018-09-14 23:13:36 +02:00
}
return nil
2018-08-16 20:53:47 +02:00
})
}
func (d *naiveNotify) watchAncestorOfMissingPath(path string) error {
if path == string(filepath.Separator) {
return fmt.Errorf("cannot watch root directory")
}
_, err := os.Stat(path)
if err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "os.Stat(%q)", path)
}
if os.IsNotExist(err) {
parent := filepath.Dir(path)
return d.watchAncestorOfMissingPath(parent)
}
return d.watcher.Add(path)
}
func (d *naiveNotify) Close() error {
2018-08-16 20:53:47 +02:00
return d.watcher.Close()
}
func (d *naiveNotify) Events() chan FileEvent {
2018-08-16 20:53:47 +02:00
return d.wrappedEvents
}
func (d *naiveNotify) Errors() chan error {
2018-08-16 20:53:47 +02:00
return d.errors
}
func (d *naiveNotify) loop() {
defer close(d.wrappedEvents)
2018-08-16 20:53:47 +02:00
for e := range d.events {
shouldNotify := d.shouldNotify(e.Name)
if e.Op&fsnotify.Create != fsnotify.Create {
if shouldNotify {
d.wrappedEvents <- FileEvent{e.Name}
2018-08-22 21:59:46 +02:00
}
continue
2018-08-22 21:59:46 +02:00
}
// TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking?
if err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
if err != nil {
return err
}
if d.shouldNotify(path) {
d.wrappedEvents <- FileEvent{path}
}
// TODO(dmiller): symlinks 😭
shouldWatch := false
if mode.IsDir() {
// watch all directories
shouldWatch = true
} else {
// watch files that are explicitly named, but don't watch others
_, ok := d.notifyList[path]
if ok {
shouldWatch = true
}
}
if shouldWatch {
err := d.watcher.Add(path)
if err != nil {
log.Printf("Error watching path %s: %s", e.Name, err)
2018-08-16 20:53:47 +02:00
}
}
return nil
}); err != nil {
log.Printf("Error walking directory %s: %s", e.Name, err)
2018-08-16 20:53:47 +02:00
}
}
}
func (d *naiveNotify) shouldNotify(path string) bool {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.notifyList[path]; ok {
return true
}
// TODO(dmiller): maybe use a prefix tree here?
for root := range d.notifyList {
if ospath.IsChild(root, path) {
return true
2018-08-16 20:53:47 +02:00
}
}
return false
2018-08-16 20:53:47 +02:00
}
func NewWatcher() (*naiveNotify, error) {
2018-08-16 20:53:47 +02:00
fsw, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
wrappedEvents := make(chan FileEvent)
2018-08-16 20:53:47 +02:00
wmw := &naiveNotify{
2018-08-16 20:53:47 +02:00
watcher: fsw,
events: fsw.Events,
wrappedEvents: wrappedEvents,
errors: fsw.Errors,
notifyList: map[string]bool{},
2018-08-16 20:53:47 +02:00
}
go wmw.loop()
return wmw, nil
}
2018-08-22 21:59:46 +02:00
func isDir(pth string) (bool, error) {
fi, err := os.Lstat(pth)
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
return fi.IsDir(), nil
2018-08-16 20:53:47 +02:00
}
var _ Notify = &naiveNotify{}