watcher_naive.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // +build !darwin
  2. package watch
  3. import (
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sync"
  8. "github.com/pkg/errors"
  9. "github.com/windmilleng/fsnotify"
  10. "github.com/windmilleng/tilt/internal/logger"
  11. "github.com/windmilleng/tilt/internal/ospath"
  12. )
  13. // A naive file watcher that uses the plain fsnotify API.
  14. // Used on all non-Darwin systems (including Windows & Linux).
  15. //
  16. // All OS-specific codepaths are handled by fsnotify.
  17. type naiveNotify struct {
  18. log logger.Logger
  19. watcher *fsnotify.Watcher
  20. events chan fsnotify.Event
  21. wrappedEvents chan FileEvent
  22. errors chan error
  23. mu sync.Mutex
  24. // Paths that we're watching that should be passed up to the caller.
  25. // Note that we may have to watch ancestors of these paths
  26. // in order to fulfill the API promise.
  27. notifyList map[string]bool
  28. }
  29. func (d *naiveNotify) Add(name string) error {
  30. fi, err := os.Stat(name)
  31. if err != nil && !os.IsNotExist(err) {
  32. return errors.Wrapf(err, "notify.Add(%q)", name)
  33. }
  34. // if it's a file that doesn't exist, watch its parent
  35. if os.IsNotExist(err) {
  36. err = d.watchAncestorOfMissingPath(name)
  37. if err != nil {
  38. return errors.Wrapf(err, "watchAncestorOfMissingPath(%q)", name)
  39. }
  40. } else if fi.IsDir() {
  41. err = d.watchRecursively(name)
  42. if err != nil {
  43. return errors.Wrapf(err, "notify.Add(%q)", name)
  44. }
  45. } else {
  46. err = d.watcher.Add(filepath.Dir(name))
  47. if err != nil {
  48. return errors.Wrapf(err, "notify.Add(%q)", filepath.Dir(name))
  49. }
  50. }
  51. d.mu.Lock()
  52. defer d.mu.Unlock()
  53. d.notifyList[name] = true
  54. return nil
  55. }
  56. func (d *naiveNotify) watchRecursively(dir string) error {
  57. return filepath.Walk(dir, func(path string, mode os.FileInfo, err error) error {
  58. if err != nil {
  59. return err
  60. }
  61. if !mode.IsDir() {
  62. return nil
  63. }
  64. err = d.watcher.Add(path)
  65. if err != nil {
  66. if os.IsNotExist(err) {
  67. return nil
  68. }
  69. return errors.Wrapf(err, "watcher.Add(%q)", path)
  70. }
  71. return nil
  72. })
  73. }
  74. func (d *naiveNotify) watchAncestorOfMissingPath(path string) error {
  75. if path == string(filepath.Separator) {
  76. return fmt.Errorf("cannot watch root directory")
  77. }
  78. _, err := os.Stat(path)
  79. if err != nil && !os.IsNotExist(err) {
  80. return errors.Wrapf(err, "os.Stat(%q)", path)
  81. }
  82. if os.IsNotExist(err) {
  83. parent := filepath.Dir(path)
  84. return d.watchAncestorOfMissingPath(parent)
  85. }
  86. return d.watcher.Add(path)
  87. }
  88. func (d *naiveNotify) Close() error {
  89. return d.watcher.Close()
  90. }
  91. func (d *naiveNotify) Events() chan FileEvent {
  92. return d.wrappedEvents
  93. }
  94. func (d *naiveNotify) Errors() chan error {
  95. return d.errors
  96. }
  97. func (d *naiveNotify) loop() {
  98. defer close(d.wrappedEvents)
  99. for e := range d.events {
  100. shouldNotify := d.shouldNotify(e.Name)
  101. if e.Op&fsnotify.Create != fsnotify.Create {
  102. if shouldNotify {
  103. d.wrappedEvents <- FileEvent{e.Name}
  104. }
  105. continue
  106. }
  107. // TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking?
  108. err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
  109. if err != nil {
  110. return err
  111. }
  112. if d.shouldNotify(path) {
  113. d.wrappedEvents <- FileEvent{path}
  114. }
  115. // TODO(dmiller): symlinks 😭
  116. shouldWatch := false
  117. if mode.IsDir() {
  118. // watch all directories
  119. shouldWatch = true
  120. } else {
  121. // watch files that are explicitly named, but don't watch others
  122. _, ok := d.notifyList[path]
  123. if ok {
  124. shouldWatch = true
  125. }
  126. }
  127. if shouldWatch {
  128. err := d.watcher.Add(path)
  129. if err != nil && !os.IsNotExist(err) {
  130. d.log.Infof("Error watching path %s: %s", e.Name, err)
  131. }
  132. }
  133. return nil
  134. })
  135. if err != nil && !os.IsNotExist(err) {
  136. d.log.Infof("Error walking directory %s: %s", e.Name, err)
  137. }
  138. }
  139. }
  140. func (d *naiveNotify) shouldNotify(path string) bool {
  141. d.mu.Lock()
  142. defer d.mu.Unlock()
  143. if _, ok := d.notifyList[path]; ok {
  144. return true
  145. }
  146. // TODO(dmiller): maybe use a prefix tree here?
  147. for root := range d.notifyList {
  148. if ospath.IsChild(root, path) {
  149. return true
  150. }
  151. }
  152. return false
  153. }
  154. func NewWatcher(l logger.Logger) (*naiveNotify, error) {
  155. fsw, err := fsnotify.NewWatcher()
  156. if err != nil {
  157. return nil, err
  158. }
  159. wrappedEvents := make(chan FileEvent)
  160. wmw := &naiveNotify{
  161. log: l,
  162. watcher: fsw,
  163. events: fsw.Events,
  164. wrappedEvents: wrappedEvents,
  165. errors: fsw.Errors,
  166. notifyList: map[string]bool{},
  167. }
  168. go wmw.loop()
  169. return wmw, nil
  170. }
  171. func isDir(pth string) (bool, error) {
  172. fi, err := os.Lstat(pth)
  173. if os.IsNotExist(err) {
  174. return false, nil
  175. } else if err != nil {
  176. return false, err
  177. }
  178. return fi.IsDir(), nil
  179. }
  180. var _ Notify = &naiveNotify{}