watcher_naive.go 4.0 KB

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