-
Notifications
You must be signed in to change notification settings - Fork 0
/
filechangeroutine.go
88 lines (81 loc) · 2.43 KB
/
filechangeroutine.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
package goodroutine
import (
"os"
"sync"
"time"
)
// FileChangeRoutine implements an interval routine that calls a function on file change.
// A file change is detected when the OS reported file ModTime or size has changed.
// Some important notes:
// - the error interval is only triggered by an error returned by the function, not by file stat error
// - the first run of Stats on file does not trigger the function (not considered a change)
// - file stat error on a file only triggers a change once
type FileChangeRoutine struct {
OnFileChange func(file string, stat os.FileInfo, err error)
innerF func() error
files []string
fileStats []os.FileInfo
once *sync.Once
IntervalRoutine
}
// NewFileChangeRoutine creates a new FileChangeRoutine, which takes care of running f().
// Parameters are equivalent to IntervalRoutine.
func NewFileChangeRoutine(f func() error, runInterval time.Duration, retryInterval time.Duration) *FileChangeRoutine {
fcr := &FileChangeRoutine{
IntervalRoutine: IntervalRoutine{
runInterval: runInterval,
retryInterval: retryInterval,
force: make(chan bool, 1),
done: make(chan bool, 1),
},
innerF: f,
once: &sync.Once{},
}
fcr.IntervalRoutine.runner = RunnerFunc(func() error {
return fcr.update()
})
return fcr
}
// AddFiles adds files to watch for updates.
// Parameter is a list of file paths, empty path are ignored.
// This function must be called prior to calling Start()
func (fcr *FileChangeRoutine) AddFiles(files ...string) {
for _, file := range files {
if file == "" {
// ignore empty files for convenience
continue
}
fcr.files = append(fcr.files, file)
fcr.fileStats = append(fcr.fileStats, nil)
}
}
func (fcr *FileChangeRoutine) update() error {
change := false
for i, file := range fcr.files {
stat, err := os.Stat(file)
ostat := fcr.fileStats[i]
if err != nil {
// error on stat, file probably does not exist or bad perm
if ostat == nil {
// no previous stat, dont trigger forever
continue
}
}
if ostat == nil || stat == nil || !stat.ModTime().Equal(ostat.ModTime()) || stat.Size() != ostat.Size() {
if fcr.OnFileChange != nil {
fcr.OnFileChange(file, stat, err)
}
change = true
fcr.fileStats[i] = stat
}
}
fcr.once.Do(func() {
// dont trigger change on 1st run, it's not a change
change = false
})
if !change {
// no error, no file change
return nil
}
return fcr.innerF()
}