/
watch.go
128 lines (110 loc) · 3.29 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
package main
import (
"github.com/mattes/fsevents"
"sort"
"strings"
"time"
)
// A list of all fsevents flags mapped to readable descriptions.
// @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/c_ref/kFSEventStreamEventFlagNone
var noteDescription = map[fsevents.EventFlags]string{
fsevents.MustScanSubDirs: "MustScanSubdirs",
fsevents.UserDropped: "UserDropped",
fsevents.KernelDropped: "KernelDropped",
fsevents.EventIDsWrapped: "EventIDsWrapped",
fsevents.HistoryDone: "HistoryDone",
fsevents.RootChanged: "RootChanged",
fsevents.Mount: "Mount",
fsevents.Unmount: "Unmount",
fsevents.ItemCreated: "Created",
fsevents.ItemRemoved: "Removed",
fsevents.ItemInodeMetaMod: "InodeMetaMod",
fsevents.ItemRenamed: "Renamed",
fsevents.ItemModified: "Modified",
fsevents.ItemFinderInfoMod: "FinderInfoMod",
fsevents.ItemChangeOwner: "ChangeOwner",
fsevents.ItemXattrMod: "XAttrMod",
fsevents.ItemIsFile: "IsFile",
fsevents.ItemIsDir: "IsDir",
fsevents.ItemIsSymlink: "IsSymLink",
}
const WATCH_POLL_INTERVAL = 5
func Watch(path string, ignored []string, poll bool, callback func(id uint64, path string, flags []string)) {
dev, _ := fsevents.DeviceForPath(path)
fsevents.EventIDForDeviceBeforeTime(dev, time.Now())
es := &fsevents.EventStream{
Paths: []string{path},
Latency: 50 * time.Millisecond,
Device: dev,
Flags: fsevents.FileEvents | fsevents.WatchRoot}
es.Start()
ec := es.Events
poller := time.NewTicker(WATCH_POLL_INTERVAL * time.Second)
// Disable the poller if polling isn't requested.
if !poll {
poller.Stop()
}
for {
select {
case event := <-ec:
if FileIsIgnored(event.Path, ignored) {
continue
}
flags := make([]string, 0)
for bit, description := range noteDescription {
if event.Flags&bit == bit {
flags = append(flags, description)
}
}
sort.Sort(sort.StringSlice(flags))
go callback(event.ID, event.Path, flags)
es.Flush(false)
case <-poller.C:
go callback(0, "", []string{})
}
}
}
// Heavily inspired by github.com/ryanuber/go-glob.
func FileIsIgnored(file string, ignored []string) bool {
for _, pattern := range ignored {
if pattern == "" {
continue
}
if pattern == IGNORE_WILDCARD {
return true
}
parts := strings.Split(pattern, IGNORE_WILDCARD)
// If no wildcards exist in the pattern, check for exact equality.
if len(parts) == 1 {
if file == pattern {
return true
}
}
leadingWildcard := strings.HasPrefix(pattern, IGNORE_WILDCARD)
trailingWildcard := strings.HasSuffix(pattern, IGNORE_WILDCARD)
end := len(parts) - 1
// Check each part of the pattern for a match.
for i, part := range parts {
switch i {
case 0:
if leadingWildcard {
// Skip if we're checking everything but the start of the string.
continue
} else if strings.HasPrefix(file, part) {
return true
}
case end:
if len(file) > 0 && !trailingWildcard && strings.HasSuffix(file, part) {
// If the filepath isn't empty, we're not checking for the end of a
// string and we have a suffix match, return true.
return true
}
default:
if strings.Contains(file, part) {
return true
}
}
}
}
return false
}