forked from cortesi/devd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwatch.go
162 lines (148 loc) · 3.73 KB
/
watch.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
package devd
import (
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/bmatcuk/doublestar"
"github.com/cortesi/devd/livereload"
"github.com/cortesi/devd/termlog"
"github.com/rjeczalik/notify"
)
const batchTime = time.Millisecond * 200
// This function batches events up, and emits just a list of paths for files
// considered changed. It applies some heuristics to deal with short-lived
// temporary files.
func batch(batchTime time.Duration, ch chan string) chan []string {
ret := make(chan []string)
go func() {
emap := make(map[string]bool)
for {
select {
case path := <-ch:
emap[path] = true
case <-time.After(batchTime):
keys := make([]string, 0, len(emap))
for k := range emap {
_, err := os.Stat(k)
if err == nil {
keys = append(keys, k)
}
}
if len(keys) > 0 {
ret <- keys
}
emap = make(map[string]bool)
}
}
}()
return ret
}
func watch(p string, ch chan string) error {
stat, err := os.Stat(p)
if err != nil {
return err
}
if stat.IsDir() {
p = path.Join(p, "...")
}
evtch := make(chan notify.EventInfo)
err = notify.Watch(p, evtch, notify.All)
if err == nil {
go func() {
for e := range evtch {
ch <- e.Path()
}
}()
}
return err
}
// Watch watches an endpoint for changes, if it supports them.
func (r Route) Watch(ch chan []string, excludePatterns []string, log termlog.Logger) error {
switch r.Endpoint.(type) {
case *filesystemEndpoint:
ep := *r.Endpoint.(*filesystemEndpoint)
pathchan := make(chan string, 1)
err := watch(string(ep), pathchan)
if err != nil {
return err
}
go func() {
for files := range batch(batchTime, pathchan) {
for i, fpath := range files {
files[i] = path.Join(
r.Path,
strings.TrimPrefix(fpath, string(ep)),
)
}
files = filterFiles("/", files, excludePatterns, log)
ch <- files
}
}()
}
return nil
}
// Determine if a file should be included, based on the given exclude paths.
func shouldInclude(file string, excludePatterns []string, log termlog.Logger) bool {
for _, pattern := range excludePatterns {
match, err := doublestar.Match(pattern, file)
if err != nil {
log.Warn("Error matching pattern '%s': %s", pattern, err)
} else if match {
return false
}
}
return true
}
// Filter out the files that match the given exclude patterns.
func filterFiles(pathPrefix string, files, excludePatterns []string, log termlog.Logger) []string {
ret := []string{}
for _, file := range files {
relFile := strings.TrimPrefix(file, pathPrefix)
if shouldInclude(relFile, excludePatterns, log) {
ret = append(ret, file)
}
}
return ret
}
// WatchPaths watches a set of paths, and broadcasts changes through reloader.
func WatchPaths(paths, excludePatterns []string, reloader livereload.Reloader, log termlog.Logger) error {
ch := make(chan []string, 1)
for _, path := range paths {
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
if absPath[len(absPath)-1] != filepath.Separator {
absPath += string(filepath.Separator)
}
pathchan := make(chan string, 1)
err = watch(path, pathchan)
if err != nil {
return err
}
go func() {
for files := range batch(batchTime, pathchan) {
files = filterFiles(absPath, files, excludePatterns, log)
if len(files) > 0 {
ch <- files
}
}
}()
}
go reloader.Watch(ch)
return nil
}
// WatchRoutes watches the route collection, and broadcasts changes through reloader.
func WatchRoutes(routes RouteCollection, reloader livereload.Reloader, excludePatterns []string, log termlog.Logger) error {
c := make(chan []string, 1)
for i := range routes {
err := routes[i].Watch(c, excludePatterns, log)
if err != nil {
return err
}
}
go reloader.Watch(c)
return nil
}