/
efsw.go
186 lines (155 loc) · 4.15 KB
/
efsw.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package efsw
/*
#cgo pkg-config: efsw
#include <stdlib.h>
#include "go_efsw.h"
*/
import "C"
import (
// System imports
"errors"
"sync"
"unsafe"
"unicode/utf8"
)
// Error definitions
var (
ErrInvalidPathEncoding = errors.New("invalid path encoding or content")
)
// Event types (set to match efsw's API)
const (
EventAdd = C.EFSW_ADD
EventDelete = C.EFSW_DELETE
EventModified = C.EFSW_MODIFIED
EventMoved = C.EFSW_MOVED
)
// Map of event types to name
var EventTypeToName = map[int]string {
EventAdd: "add",
EventDelete: "delete",
EventModified: "modified",
EventMoved: "moved",
}
// Type representing filesystem events
type Event struct {
Directory string
Filename string
Type int
OldFilename string
}
// Type representing a watch
type Watch struct {
watchId C.efsw_watchid
Events chan Event
}
// Global watcher
var watcher C.efsw_watcher
// Global map from watch id to event channel (necessary since we can't associate
// a persistent (non-GC'd) pointer with the C callback)
var dispatchMap = make(map[C.efsw_watchid]chan Event)
// Global lock designed to coordinate access to the efsw API (which is not
// thread-safe and needs a write lock for use) and the watch-to-channel map
// (which requires a read lock for dispatch and a write lock for mutation). We
// could use an RWMutex here, which would improve performance if we had many
// watcher threads dispatching events, but we only have one watcher thread
// locking it for reads, so there won't be any contention between readers
// anyway.
var lock sync.Mutex
// Watcher callback
//export watcherCallback
func watcherCallback(
watchId C.efsw_watchid,
directory,
filename *C.char,
action C.int,
oldFilename *C.char,
) {
// Lock the watch-to-channel map for reading
lock.Lock()
defer lock.Unlock()
// Get the event channel, ignoring the callback if we can't find it, because
// the channel may have been removed from the dispatch map by DeleteWatch
// before the watcher thread managed to post the event
channel := dispatchMap[watchId]
if channel == nil {
return
}
// Create the event
event := Event{
C.GoString(directory),
C.GoString(filename),
int(action),
C.GoString(oldFilename),
}
// Dispatch the event if immediately possible, otherwise discard it
select {
case channel <- event:
default:
}
}
// Validates a string as UTF-8 with no null bytes
func isValidNonNullUTF8(s string) bool {
// Check that this is valid UTF-8
if !utf8.ValidString(s) {
return false
}
// Check that there are no null-bytes (which are allowed by UTF-8)
for i := 0; i < len(s); i++ {
if s[i] == 0 {
return false
}
}
// All done
return true
}
// Watch factory. The path argument should be UTF-8 encoded.
func NewWatch(path string, recursive bool, buffer int) (*Watch, error) {
// Validate the path
if !isValidNonNullUTF8(path) {
return nil, ErrInvalidPathEncoding
}
// Lock the API and watch-to-channel map for writing
lock.Lock()
defer lock.Unlock()
// Ensure that the global watcher has been initialized
if watcher == nil {
// Allocate the watcher
watcher = C.efsw_create(0)
// Tell it to launch its watching thread
C.efsw_watch(watcher)
}
// Convert the path
pathCString := C.CString(path)
defer C.free(unsafe.Pointer(pathCString))
// Convert the recursive option
recursiveCInt := C.int(1)
if !recursive {
recursiveCInt = 0
}
// Add the watch (we don't need to worry about it firing a callback before
// the channel is in place because we're holding the lock at the moment)
watchId := C.go_efsw_add_watch(
watcher,
pathCString,
recursiveCInt,
)
// Create the event channel
channel := make(chan Event, buffer)
// Record the channel in the dispatch map
dispatchMap[watchId] = channel
// All done
return &Watch{watchId, channel}, nil
}
// Removes a watch
func DeleteWatch(watch *Watch) {
// Lock the API and watch-to-channel map for writing
lock.Lock()
defer lock.Unlock()
// Remove the watch
C.efsw_removewatch_byid(watcher, watch.watchId)
// Remove the entry from the dispatch map
delete(dispatchMap, watch.watchId)
// Close the channel (will be fine since it's no longer in the dispatch map
// and the watcher thread can't write to it)
close(watch.Events)
}