github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/watcherx/directory.go (about) 1 package watcherx 2 3 import ( 4 "context" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "time" 9 10 "github.com/fsnotify/fsnotify" 11 "github.com/pkg/errors" 12 ) 13 14 func WatchDirectory(ctx context.Context, dir string, c EventChannel) (Watcher, error) { 15 w, err := fsnotify.NewWatcher() 16 if err != nil { 17 return nil, errors.WithStack(err) 18 } 19 var subDirs []string 20 if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 21 if err != nil { 22 return err 23 } 24 if info.IsDir() { 25 subDirs = append(subDirs, path) 26 } 27 return nil 28 }); err != nil { 29 return nil, errors.WithStack(err) 30 } 31 for _, d := range append(subDirs, dir) { 32 if err := w.Add(d); err != nil { 33 return nil, errors.WithStack(err) 34 } 35 } 36 37 d := newDispatcher() 38 go streamDirectoryEvents(ctx, w, c, d.trigger, d.done, dir) 39 return d, nil 40 } 41 42 func handleEvent(e fsnotify.Event, w *fsnotify.Watcher, c EventChannel) { 43 if e.Op&fsnotify.Remove != 0 { 44 // We cannot figure out anymore if it was a file or directory. 45 // If it was a directory it was added to the watchers as well as it's parent. 46 // Therefore we will get two consecutive remove events from inotify (REMOVE and REMOVE_SELF). 47 // Sometimes the second event has an empty name (no specific reason for that). 48 // If there is no second event (timeout 1ms) we assume it was a file that got deleted. 49 // This means that file deletion events are delayed by 1ms. 50 select { 51 case <-time.After(time.Millisecond): 52 c <- &RemoveEvent{ 53 source: source(e.Name), 54 } 55 return 56 case secondE := <-w.Events: 57 if (secondE.Name != "" && secondE.Name != e.Name) || secondE.Op&fsnotify.Remove == 0 { 58 // this is NOT the unix.IN_DELETE_SELF event => we have to handle the first explicitly 59 // and the second recursively because it might be the first event of a directory deletion 60 c <- &RemoveEvent{ 61 source: source(e.Name), 62 } 63 handleEvent(secondE, w, c) 64 } // else we do not want any event on deletion of a folder 65 } 66 } else if e.Op&(fsnotify.Write|fsnotify.Create) != 0 { 67 if stats, err := os.Stat(e.Name); err != nil { 68 c <- &ErrorEvent{ 69 error: errors.WithStack(err), 70 source: source(e.Name), 71 } 72 return 73 } else if stats.IsDir() { 74 if err := w.Add(e.Name); err != nil { 75 c <- &ErrorEvent{ 76 error: errors.WithStack(err), 77 source: source(e.Name), 78 } 79 } 80 return 81 } 82 data, err := ioutil.ReadFile(e.Name) 83 if err != nil { 84 c <- &ErrorEvent{ 85 error: err, 86 source: source(e.Name), 87 } 88 } else { 89 c <- &ChangeEvent{ 90 data: data, 91 source: source(e.Name), 92 } 93 } 94 } 95 } 96 97 func streamDirectoryEvents(ctx context.Context, w *fsnotify.Watcher, c EventChannel, sendNow <-chan struct{}, sendNowDone chan<- int, dir string) { 98 for { 99 select { 100 case <-ctx.Done(): 101 _ = w.Close() 102 return 103 case e := <-w.Events: 104 handleEvent(e, w, c) 105 case <-sendNow: 106 var eventsSent int 107 108 if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 109 if err != nil { 110 return err 111 } 112 if !info.IsDir() { 113 data, err := ioutil.ReadFile(path) 114 if err != nil { 115 c <- &ErrorEvent{ 116 error: err, 117 source: source(path), 118 } 119 } else { 120 c <- &ChangeEvent{ 121 data: data, 122 source: source(path), 123 } 124 } 125 eventsSent++ 126 } 127 return nil 128 }); err != nil { 129 c <- &ErrorEvent{ 130 error: err, 131 source: source(dir), 132 } 133 eventsSent++ 134 } 135 136 sendNowDone <- eventsSent 137 } 138 } 139 }