2019-01-02 17:18:33 +01:00
|
|
|
// +build !darwin
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
package watch
|
|
|
|
|
|
|
|
import (
|
2019-01-16 20:57:50 +01:00
|
|
|
"fmt"
|
2018-08-16 20:53:47 +02:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
2018-11-30 18:05:15 +01:00
|
|
|
"github.com/pkg/errors"
|
2018-08-16 20:53:47 +02:00
|
|
|
"github.com/windmilleng/fsnotify"
|
2019-04-01 22:51:43 +02:00
|
|
|
|
2019-06-08 00:03:02 +02:00
|
|
|
"github.com/windmilleng/tilt/internal/logger"
|
2019-04-01 22:51:43 +02:00
|
|
|
"github.com/windmilleng/tilt/internal/ospath"
|
2018-08-16 20:53:47 +02:00
|
|
|
)
|
|
|
|
|
2019-01-02 17:18:33 +01: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 {
|
2019-03-22 21:17:12 +01:00
|
|
|
// 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
|
2019-07-11 17:40:40 +02:00
|
|
|
|
|
|
|
ignore PathMatcher
|
|
|
|
log logger.Logger
|
|
|
|
|
|
|
|
watcher *fsnotify.Watcher
|
|
|
|
events chan fsnotify.Event
|
|
|
|
wrappedEvents chan FileEvent
|
|
|
|
errors chan error
|
2019-07-11 23:54:50 +02:00
|
|
|
numWatches int64
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-07-11 17:40:40 +02:00
|
|
|
func (d *naiveNotify) Start() error {
|
|
|
|
for name := range d.notifyList {
|
|
|
|
fi, err := os.Stat(name)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2018-11-30 18:05:15 +01:00
|
|
|
return errors.Wrapf(err, "notify.Add(%q)", name)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
2019-07-11 17:40:40 +02:00
|
|
|
|
|
|
|
// if it's a file that doesn't exist, watch its parent
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = d.watchAncestorOfMissingPath(name)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "watchAncestorOfMissingPath(%q)", name)
|
|
|
|
}
|
|
|
|
} else if fi.IsDir() {
|
|
|
|
err = d.watchRecursively(name)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "notify.Add(%q)", name)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = d.add(filepath.Dir(name))
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "notify.Add(%q)", filepath.Dir(name))
|
|
|
|
}
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-14 21:05:03 +02:00
|
|
|
|
2019-07-11 17:40:40 +02:00
|
|
|
go d.loop()
|
2018-08-16 20:53:47 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:05:03 +02:00
|
|
|
if !mode.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-01 17:44:30 +02:00
|
|
|
err = d.add(path)
|
2018-09-14 23:13:36 +02:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
2018-11-30 18:05:15 +01:00
|
|
|
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
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-22 21:17:12 +01:00
|
|
|
func (d *naiveNotify) watchAncestorOfMissingPath(path string) error {
|
2019-01-16 20:57:50 +01:00
|
|
|
if path == string(filepath.Separator) {
|
2019-03-22 21:17:12 +01:00
|
|
|
return fmt.Errorf("cannot watch root directory")
|
2019-01-16 20:57:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2019-03-22 21:17:12 +01:00
|
|
|
return errors.Wrapf(err, "os.Stat(%q)", path)
|
2019-01-16 20:57:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
parent := filepath.Dir(path)
|
2019-03-22 21:17:12 +01:00
|
|
|
return d.watchAncestorOfMissingPath(parent)
|
2019-01-16 20:57:50 +01:00
|
|
|
}
|
|
|
|
|
2019-07-01 17:44:30 +02:00
|
|
|
return d.add(path)
|
2019-01-16 20:57:50 +01:00
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
func (d *naiveNotify) Close() error {
|
2019-07-11 23:54:50 +02:00
|
|
|
numberOfWatches.Add(-d.numWatches)
|
|
|
|
d.numWatches = 0
|
2018-08-16 20:53:47 +02:00
|
|
|
return d.watcher.Close()
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
func (d *naiveNotify) Events() chan FileEvent {
|
2018-08-16 20:53:47 +02:00
|
|
|
return d.wrappedEvents
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
func (d *naiveNotify) Errors() chan error {
|
2018-08-16 20:53:47 +02:00
|
|
|
return d.errors
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
func (d *naiveNotify) loop() {
|
2019-05-14 21:05:03 +02:00
|
|
|
defer close(d.wrappedEvents)
|
2018-08-16 20:53:47 +02:00
|
|
|
for e := range d.events {
|
2019-05-14 21:05:03 +02:00
|
|
|
if e.Op&fsnotify.Create != fsnotify.Create {
|
2019-07-11 17:40:40 +02:00
|
|
|
if d.shouldNotify(e.Name) {
|
2019-05-14 21:05:03 +02:00
|
|
|
d.wrappedEvents <- FileEvent{e.Name}
|
2018-08-22 21:59:46 +02:00
|
|
|
}
|
2019-05-14 21:05:03 +02:00
|
|
|
continue
|
2018-08-22 21:59:46 +02:00
|
|
|
}
|
2019-03-22 21:17:12 +01:00
|
|
|
|
2019-05-14 21:05:03 +02:00
|
|
|
// TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking?
|
2019-06-08 00:03:02 +02:00
|
|
|
err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
|
2019-05-14 21:05:03 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-03-22 21:17:12 +01:00
|
|
|
|
2019-05-14 21:05:03 +02:00
|
|
|
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 {
|
2019-07-01 17:44:30 +02:00
|
|
|
err := d.add(path)
|
2019-06-08 00:03:02 +02:00
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
d.log.Infof("Error watching path %s: %s", e.Name, err)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-14 21:05:03 +02:00
|
|
|
return nil
|
2019-06-08 00:03:02 +02:00
|
|
|
})
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
d.log.Infof("Error walking directory %s: %s", e.Name, err)
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:05:03 +02:00
|
|
|
func (d *naiveNotify) shouldNotify(path string) bool {
|
2019-07-11 17:40:40 +02:00
|
|
|
ignore, err := d.ignore.Matches(path)
|
|
|
|
if err != nil {
|
|
|
|
d.log.Infof("Error matching path %q: %v", path, err)
|
|
|
|
} else if ignore {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:05:03 +02:00
|
|
|
if _, ok := d.notifyList[path]; ok {
|
2019-03-22 21:17:12 +01:00
|
|
|
return true
|
2019-05-14 21:05:03 +02:00
|
|
|
}
|
|
|
|
// 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
|
|
|
}
|
|
|
|
}
|
2019-03-22 21:17:12 +01:00
|
|
|
return false
|
2018-08-16 20:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 17:44:30 +02:00
|
|
|
func (d *naiveNotify) add(path string) error {
|
|
|
|
err := d.watcher.Add(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-11 23:54:50 +02:00
|
|
|
d.numWatches++
|
2019-07-01 17:44:30 +02:00
|
|
|
numberOfWatches.Add(1)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-11 17:40:40 +02:00
|
|
|
func newWatcher(paths []string, ignore PathMatcher, l logger.Logger) (*naiveNotify, error) {
|
|
|
|
if ignore == nil {
|
|
|
|
return nil, fmt.Errorf("newWatcher: ignore is nil")
|
|
|
|
}
|
|
|
|
|
2018-08-16 20:53:47 +02:00
|
|
|
fsw, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-08-22 21:33:06 +02:00
|
|
|
wrappedEvents := make(chan FileEvent)
|
2019-07-11 17:40:40 +02:00
|
|
|
notifyList := make(map[string]bool, len(paths))
|
|
|
|
for _, path := range paths {
|
2019-07-13 20:13:24 +02:00
|
|
|
path, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "newWatcher")
|
|
|
|
}
|
2019-07-11 17:40:40 +02:00
|
|
|
notifyList[path] = true
|
|
|
|
}
|
2018-08-16 20:53:47 +02:00
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
wmw := &naiveNotify{
|
2019-07-11 17:40:40 +02:00
|
|
|
notifyList: notifyList,
|
|
|
|
ignore: ignore,
|
2019-06-08 00:03:02 +02:00
|
|
|
log: l,
|
2018-08-16 20:53:47 +02:00
|
|
|
watcher: fsw,
|
|
|
|
events: fsw.Events,
|
|
|
|
wrappedEvents: wrappedEvents,
|
|
|
|
errors: fsw.Errors,
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-01-02 17:18:33 +01:00
|
|
|
var _ Notify = &naiveNotify{}
|