-
Notifications
You must be signed in to change notification settings - Fork 0
/
watchdir.go
138 lines (117 loc) · 3.4 KB
/
watchdir.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"os"
"path/filepath"
"time"
)
const debounceCount int = 15
const debounceInterval time.Duration = 1 * time.Second
// A Handler is a function that handles events for WatchDir.
type Handler func(WatcherEvent) error
// Watch watches a directory for file system changes until the watcher is
// stopped or fails to watch a sub-directory, or the handler returns an error.
func Watch(dir string, watcher Watcher, handle Handler) error {
dw := dirWatcher{
walkDirs,
isDir,
watcher,
debounceCount,
debounceInterval,
}
return dw.watch(dir, handle)
}
// The context for watching a directory.
type dirWatcher struct {
// A function that walks the directory tree of a given directory path and
// calls a given function on each directory path.
walkDirs func(string, func(string) error) error
// A function that tests whether a given path refers to a directory.
isDir func(string) bool
// The Watcher to use to detect file system changes.
watcher Watcher
// The max number of events to debounce.
debounceCount int
// The interval within which subsequent events must occur to trigger
// debouncing.
debounceInterval time.Duration
}
// Implements WatchDir using the target dirWatcher.
func (dw *dirWatcher) watch(dir string, handle Handler) error {
if err := dw.watchDir(dir); err != nil {
return err
}
events := dw.watcher.Events()
for {
event, ok := <-events
if !ok {
return nil
}
if err := dw.processEvent(event); err != nil {
return err
}
// Debounce the event queue a bit. Our command will reflect the state
// of the system when it runs so we don't actually care about the
// of the events other than whether one should trigger a re-run.
DEBOUNCE:
for i := 0; i < dw.debounceCount; i++ {
select {
case e := <-events:
if err := dw.processEvent(e); err != nil {
return err
}
break
case <-time.After(dw.debounceInterval):
break DEBOUNCE
}
}
if err := handle(event); err != nil {
return err
}
}
}
// Adds watchers to the given directory and all of its subdirectories.
func (dw *dirWatcher) watchDir(dir string) error {
return dw.walkDirs(dir, func(path string) error {
return dw.watcher.Watch(path)
})
}
// Encapsulates the dirWatcher's own handling of the event. This exists
// primarily to separate the dirWatcher's handling from the Handler since the
// latter is debounced and the former is not.
func (dw *dirWatcher) processEvent(event WatcherEvent) error {
if event.Error != nil {
logger.Printf("watchdir error event: %v\n", event.Error)
return nil
}
logger.Printf("watchdir fs event: %v\n", event.Event)
path := event.Event.Path
if event.Event.Type == Create && dw.isDir(path) {
if err := dw.watchDir(path); err != nil {
logger.Printf("watchdir watch error: %s %v\n", path, err)
return err
}
}
return nil
}
// Walks the real file system calling walkFn on the directory entries it finds.
func walkDirs(path string, walkFn func(string) error) error {
return filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
if err != nil {
logger.Printf("watchdir walk error: %v\n", err)
return err
}
if fi.IsDir() {
return walkFn(p)
}
return nil
})
}
// Tests whether the given path is a directory in the real file system.
func isDir(path string) bool {
fi, err := os.Stat(path)
if err != nil {
logger.Printf("watchdir stat error: %s %v\n", path, err)
return false
}
return fi.IsDir()
}