-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwatcher.go
208 lines (172 loc) · 5.86 KB
/
watcher.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package watcher
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"golang.org/x/sync/errgroup"
)
// Watcher represents a wrapper around `fsnotify` complete with support for its
// own callbacks for all supported event types.
type Watcher struct {
fsnotify *fsnotify.Watcher // Instance of `fsnotify` wrapped by this package.
done chan bool // A signal channel used to exit the wait loop.
hasCallbacks bool // True if any callbacks have been assigned to any supported `fsnotify.Event` event.
onAll, onRemove, onCreate, onWrite, onRename, onChmod func(fsnotify.Event, os.FileInfo, error) error // Dedicated optional callback functions for each specific `fsnotify.Event` type.
}
// New creates a new instance of a Watcher struct.
func New() (*Watcher, error) {
fsn, _ := fsnotify.NewWatcher()
return &Watcher{
fsnotify: fsn,
done: make(chan bool, 1),
}, nil
}
// AddFile adds a single valid file to the current Watcher instance and returns
// an error if the file is not valid.
func (w *Watcher) AddPath(path string) error {
return w.fsnotify.Add(path)
}
// AddDir will recursively walk the specified directory tree and add all valid
// files to the current watcher instance for monitoring.
func (w *Watcher) WalkPath(path string) error {
err := filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
w.AddPath(path)
}
return nil
})
return err
}
// AddGlob will monitor the specified "glob" pattern and add all valid files to
// the current watcher instance for monitoring.
func (w *Watcher) AddGlob(pattern string) error {
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, file := range files {
w.AddPath(file)
}
return nil
}
// On fires off an assigned callback for each event type. Only specified events
// are supported and all will return either nil or an error. Every watcher
// instance exits when it first encounters an error.
func (w *Watcher) On(event fsnotify.Op, f func(fsnotify.Event, os.FileInfo, error) error) error {
switch event {
case fsnotify.Write:
w.onWrite = f
case fsnotify.Create:
w.onCreate = f
case fsnotify.Remove:
w.onRemove = f
case fsnotify.Rename:
w.onRename = f
case fsnotify.Chmod:
w.onChmod = f
default:
return fmt.Errorf("event %s not supported", event)
}
w.hasCallbacks = true
return nil
}
// All will fire off the specified callback on any supported `fsnotify` event.
func (w *Watcher) All(f func(fsnotify.Event, os.FileInfo, error) error) {
w.onAll = f
w.hasCallbacks = true
}
// getWriteCallbackOrNil is a convenience method used to access the relevant callback.
func (w *Watcher) getWriteCallbackOrNil(event fsnotify.Event, info os.FileInfo, err error) error {
if w.onWrite != nil {
return w.onWrite(event, info, err)
}
return nil
}
// getCreateCallbackOrNil is a convenience method used to access the relevant callback.
func (w *Watcher) getCreateCallbackOrNil(event fsnotify.Event, info os.FileInfo, err error) error {
if w.onCreate != nil {
return w.onCreate(event, info, err)
}
return nil
}
// getRemoveCallbackOrNil is a convenience method used to access the relevant callback.
func (w *Watcher) getRemoveCallbackOrNil(event fsnotify.Event, info os.FileInfo, err error) error {
if w.onRemove != nil {
return w.onRemove(event, info, err)
}
return nil
}
// getRenameCallbackOrNil is a convenience method used to access the relevant callback.
func (w *Watcher) getRenameCallbackOrNil(event fsnotify.Event, info os.FileInfo, err error) error {
if w.onRename != nil {
return w.onRename(event, info, err)
}
return nil
}
// getChmodCallbackOrNil is a convenience method used to access the relevant callback.
func (w *Watcher) getChmodCallbackOrNil(event fsnotify.Event, info os.FileInfo, err error) error {
if w.onChmod != nil {
return w.onChmod(event, info, err)
}
return nil
}
// Watch creates a new `errgroup` instance and monitors for changes to any of
// the specified files. All supported event types will fire off specified
// callbacks if available. This method exits on the first encountered error.
func (w *Watcher) Watch() error {
var group errgroup.Group
if len(w.List()) == 0 {
return errors.New("no files specified to watch")
}
if !w.hasCallbacks {
return errors.New("no event type callbacks have been defined; nothing to process")
}
group.Go(func() error {
for {
select {
case event := <-w.fsnotify.Events:
info, err := os.Stat(event.Name)
switch {
case event.Op&fsnotify.Write == fsnotify.Write:
err = w.getWriteCallbackOrNil(event, info, err)
case event.Op&fsnotify.Create == fsnotify.Create:
err = w.getCreateCallbackOrNil(event, info, err)
case event.Op&fsnotify.Remove == fsnotify.Remove:
err = w.getRemoveCallbackOrNil(event, info, err)
case event.Op&fsnotify.Rename == fsnotify.Rename:
err = w.getRenameCallbackOrNil(event, info, err)
case event.Op&fsnotify.Chmod == fsnotify.Chmod:
err = w.getChmodCallbackOrNil(event, info, err)
}
if w.onAll != nil {
err = w.onAll(event, info, err)
}
if err != nil {
return err
}
case <-w.done:
w.fsnotify.Close()
close(w.done)
return nil
}
}
})
return group.Wait()
}
// List is a wrapper around `fsnotify.Watchlist()`. It returns a list of strings
// representing all files and directories currently monitored instance of
// `fsnotify`.
func (w *Watcher) List() []string {
return w.fsnotify.WatchList()
}
// Done signals a blocking channel that processing is complete and that we can
// safely exit the current watcher instance.
func (w *Watcher) Done() {
w.done <- true
}