...
1 package watcherx
2
3 import (
4 "context"
5 "io/ioutil"
6 "os"
7 "path/filepath"
8
9 "github.com/fsnotify/fsnotify"
10 "github.com/pkg/errors"
11 )
12
13 func WatchFile(ctx context.Context, file string, c EventChannel) (Watcher, error) {
14 watcher, err := fsnotify.NewWatcher()
15 if err != nil {
16 close(c)
17 return nil, errors.WithStack(err)
18 }
19 dir := filepath.Dir(file)
20 if err := watcher.Add(dir); err != nil {
21 close(c)
22 return nil, errors.WithStack(err)
23 }
24 resolvedFile, err := filepath.EvalSymlinks(file)
25 if err != nil {
26 if _, ok := err.(*os.PathError); !ok {
27 close(c)
28 return nil, errors.WithStack(err)
29 }
30
31
32 resolvedFile = ""
33 } else if resolvedFile != file {
34
35
36
37 if err := watcher.Add(file); err != nil {
38 close(c)
39 return nil, errors.WithStack(err)
40 }
41 }
42 d := newDispatcher()
43 go streamFileEvents(ctx, watcher, c, d.trigger, d.done, file, resolvedFile)
44 return d, nil
45 }
46
47
48
49 func streamFileEvents(ctx context.Context, watcher *fsnotify.Watcher, c EventChannel, sendNow <-chan struct{}, sendNowDone chan<- int, watchedFile, resolvedFile string) {
50 defer close(c)
51 eventSource := source(watchedFile)
52 removeDirectFileWatcher := func() {
53 _ = watcher.Remove(watchedFile)
54 }
55 addDirectFileWatcher := func() {
56
57
58 if _, err := os.Lstat(watchedFile); err == nil {
59 if err := watcher.Add(watchedFile); err != nil {
60 c <- &ErrorEvent{
61 error: errors.WithStack(err),
62 source: eventSource,
63 }
64 }
65 }
66 }
67 for {
68 select {
69 case <-ctx.Done():
70 _ = watcher.Close()
71 return
72 case <-sendNow:
73 if resolvedFile == "" {
74
75 c <- &RemoveEvent{eventSource}
76 } else {
77
78 data, err := ioutil.ReadFile(watchedFile)
79 if err != nil {
80 c <- &ErrorEvent{
81 error: errors.WithStack(err),
82 source: eventSource,
83 }
84 continue
85 }
86 c <- &ChangeEvent{
87 data: data,
88 source: eventSource,
89 }
90 }
91
92
93 sendNowDone <- 1
94 case e, ok := <-watcher.Events:
95 if !ok {
96 return
97 }
98
99
100 if filepath.Clean(e.Name) == watchedFile {
101 recentlyResolvedFile, err := filepath.EvalSymlinks(watchedFile)
102
103 if err != nil {
104
105 if _, ok := err.(*os.PathError); ok {
106 c <- &RemoveEvent{eventSource}
107 removeDirectFileWatcher()
108 continue
109 }
110 c <- &ErrorEvent{
111 error: errors.WithStack(err),
112 source: eventSource,
113 }
114 continue
115 }
116
117
118
119
120 switch {
121 case recentlyResolvedFile != resolvedFile:
122 resolvedFile = recentlyResolvedFile
123
124 removeDirectFileWatcher()
125 addDirectFileWatcher()
126
127 fallthrough
128 case e.Op&(fsnotify.Write|fsnotify.Create) != 0:
129 data, err := ioutil.ReadFile(watchedFile)
130 if err != nil {
131 c <- &ErrorEvent{
132 error: errors.WithStack(err),
133 source: eventSource,
134 }
135 continue
136 }
137 c <- &ChangeEvent{
138 data: data,
139 source: eventSource,
140 }
141 }
142 }
143 }
144 }
145 }
146
View as plain text