-
Notifications
You must be signed in to change notification settings - Fork 2
/
watcher.go
144 lines (121 loc) · 4.06 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
package fsmonitor
import (
"time"
"os"
"path/filepath"
"regexp"
)
// Watcher abstracts logics of discovering changes within the given file system.
type Watcher interface{
/* nested Notice channel is send-only of send-only, giving Monitor fully control
* error channel is receive-only, Watcher keep it fully controllable
*/
// Returns internal controlling channels between Monitor and Watcher
// Runs discovering logic once every time.Tick on given resource handle
Watch() (chan<- chan<- Notice, <-chan error)
}
// pathScanner implements Watcher based on filepath.Walk.
type pathScanner struct{
address string
pattern []regexp.Regexp
lastCheck map[string]os.FileInfo
}
// Watch traverses the given directory and sub-directories and sends changes since last check.
// Note: no need to use mutex is because the design is to
// run Watch() every time.Tick duration AFTER every execution of Watch()
// means Watch() must finishes in order to trigger next time.Tick()
func (s *pathScanner) Watch() (chan<- chan<- Notice, <-chan error) {
/* nested channel to coordinate Monitor and Watcher during termination
* Declared as channel of send-only channel as golang doesn't do
* implicit conversion for inner channel type, though a regular channel will
* be converted to a send-only/receive-only channel when you send it
* to a channel of send-only/receive-only channel.
*/
ncc := make(chan chan<- Notice)
errors := make(chan error)
/* Will return when ncc is closed by Monitor, and every scan is guaranteed to be complete scan
* as filepath.Walk is blocking operation, and close of Notice channel depends on
* close of error channel from this goroutine. Monitor will choose to ignore notices
* up to the length of notice channel
* nested channel explicitly used as <-chan chan<- & status channel explicitly used as chan<-
*/
go func(ncc <-chan chan<- Notice, errors chan<- error){
/* close here so to release termination handling in Watch() */
defer close(errors)
for changed:= range ncc{
Logger.Printf("Scanning kicked off!")
visited := make(map[string]os.FileInfo)
created := 0
err := filepath.Walk(s.address, func(file string, info os.FileInfo, err error) error {
if info.IsDir() {
return err
}
matched := false || len(s.pattern) == 0
for _, re := range s.pattern {
if re.FindStringIndex(file) != nil {
matched = true
break
}
}
if !matched {
return err
}
if oldinfo, ok := s.lastCheck[file]; ok {
if info.ModTime().After(oldinfo.ModTime()) {
changed <- &fileSystemNotice{
path: file,
fileinfo: info,
timestamp: time.Now(),
event: FileUpdate,
}
} else if oldinfo.Size() != info.Size() {
changed <- &fileSystemNotice{
path: file,
fileinfo: info,
timestamp: time.Now(),
event: FileUpdate,
}
}
} else if s.lastCheck != nil {
changed <- &fileSystemNotice{
path: file,
fileinfo: info,
timestamp: time.Now(),
event: FileCreate,
}
created += 1
}
visited[file] = info
return err
})
if s.lastCheck != nil && len(s.lastCheck) > (len(visited)-created) {
for file, info := range s.lastCheck {
if _, ok := visited[file]; !ok {
changed <- &fileSystemNotice{
path: file,
fileinfo: info,
timestamp: time.Now(),
event: FileRemove,
}
}
}
}
s.lastCheck = visited
Logger.Printf("Scanning finalized!")
errors <- err
}
/* all passed channels will be implicitly converted to desired one */
}(ncc, errors)
/* all returned channels will be implicitly converted to desired one */
return ncc, errors
}
// fileScanner implements Watcher by loading in a specifically formatted text as virtual file system.
type fileScanner struct{
address string
pattern []regexp.Regexp
lastCheck map[string]os.FileInfo
}
// Watch reads the listing file, compare and sends changes since last check.
func (s *fileScanner) Watch() (ncc chan<- chan<- Notice, errors <-chan error) {
return
}