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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module allows you to monitor files or directories for changes using
## asyncio.
##
## **Warning**: This module will likely disappear soon and be moved into a
## new Nimble package.
##
## Windows support is not yet implemented.
##
## **Note:** This module uses ``inotify`` on Linux (Other Unixes are not yet
## supported). ``inotify`` was merged into the 2.6.13 Linux kernel, this
## module will therefore not work with any Linux kernel prior to that, unless
## it has been patched to support inotify.
when defined(linux) or defined(nimdoc):
from posix import read
else:
{.error: "Your platform is not supported.".}
import inotify, os, asyncio, tables
type
FSMonitor* = ref FSMonitorObj
FSMonitorObj = object of RootObj
fd: cint
handleEvent: proc (m: FSMonitor, ev: MonitorEvent) {.closure.}
targets: Table[cint, string]
MonitorEventType* = enum ## Monitor event type
MonitorAccess, ## File was accessed.
MonitorAttrib, ## Metadata changed.
MonitorCloseWrite, ## Writable file was closed.
MonitorCloseNoWrite, ## Non-writable file closed.
MonitorCreate, ## Subfile was created.
MonitorDelete, ## Subfile was deleted.
MonitorDeleteSelf, ## Watched file/directory was itself deleted.
MonitorModify, ## File was modified.
MonitorMoveSelf, ## Self was moved.
MonitorMoved, ## File was moved.
MonitorOpen, ## File was opened.
MonitorAll ## Filter for all event types.
MonitorEvent* = object
case kind*: MonitorEventType ## Type of the event.
of MonitorMoveSelf, MonitorMoved:
oldPath*: string ## Old absolute location
newPath*: string ## New absolute location
else:
fullname*: string ## Absolute filename of the file/directory affected.
name*: string ## Non absolute filepath of the file/directory
## affected relative to the directory watched.
## "" if this event refers to the file/directory
## watched.
wd*: cint ## Watch descriptor.
{.deprecated: [PFSMonitor: FSMonitor, TFSMonitor: FSMonitorObj,
TMonitorEventType: MonitorEventType, TMonitorEvent: MonitorEvent].}
const
MaxEvents = 100
proc newMonitor*(): FSMonitor =
## Creates a new file system monitor.
new(result)
result.targets = initTable[cint, string]()
result.fd = inotifyInit()
if result.fd < 0:
raiseOSError(osLastError())
proc add*(monitor: FSMonitor, target: string,
filters = {MonitorAll}): cint {.discardable.} =
## Adds ``target`` which may be a directory or a file to the list of
## watched paths of ``monitor``.
## You can specify the events to report using the ``filters`` parameter.
var INFilter = 0
for f in filters:
case f
of MonitorAccess: INFilter = INFilter or IN_ACCESS
of MonitorAttrib: INFilter = INFilter or IN_ATTRIB
of MonitorCloseWrite: INFilter = INFilter or IN_CLOSE_WRITE
of MonitorCloseNoWrite: INFilter = INFilter or IN_CLOSE_NO_WRITE
of MonitorCreate: INFilter = INFilter or IN_CREATE
of MonitorDelete: INFilter = INFilter or IN_DELETE
of MonitorDeleteSelf: INFilter = INFilter or IN_DELETE_SELF
of MonitorModify: INFilter = INFilter or IN_MODIFY
of MonitorMoveSelf: INFilter = INFilter or IN_MOVE_SELF
of MonitorMoved: INFilter = INFilter or IN_MOVED_FROM or IN_MOVED_TO
of MonitorOpen: INFilter = INFilter or IN_OPEN
of MonitorAll: INFilter = INFilter or IN_ALL_EVENTS
result = inotifyAddWatch(monitor.fd, target, INFilter.uint32)
if result < 0:
raiseOSError(osLastError())
monitor.targets.add(result, target)
proc del*(monitor: FSMonitor, wd: cint) =
## Removes watched directory or file as specified by ``wd`` from ``monitor``.
##
## If ``wd`` is not a part of ``monitor`` an EOS error is raised.
if inotifyRmWatch(monitor.fd, wd) < 0:
raiseOSError(osLastError())
proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
result = @[]
let size = (sizeof(INotifyEvent)+2000)*MaxEvents
var buffer = newString(size)
let le = read(fd, addr(buffer[0]), size)
var movedFrom = initTable[cint, tuple[wd: cint, old: string]]()
var i = 0
while i < le:
var event = cast[ptr INotifyEvent](addr(buffer[i]))
var mev: MonitorEvent
mev.wd = event.wd
if event.len.int != 0:
let cstr = event.name.addr.cstring
mev.name = $cstr
else:
mev.name = ""
if (event.mask.int and IN_MOVED_FROM) != 0:
# Moved from event, add to m's collection
movedFrom.add(event.cookie.cint, (mev.wd, mev.name))
inc(i, sizeof(INotifyEvent) + event.len.int)
continue
elif (event.mask.int and IN_MOVED_TO) != 0:
mev.kind = MonitorMoved
assert movedFrom.hasKey(event.cookie.cint)
# Find the MovedFrom event.
mev.oldPath = movedFrom[event.cookie.cint].old
mev.newPath = "" # Set later
# Delete it from the Table
movedFrom.del(event.cookie.cint)
elif (event.mask.int and IN_ACCESS) != 0: mev.kind = MonitorAccess
elif (event.mask.int and IN_ATTRIB) != 0: mev.kind = MonitorAttrib
elif (event.mask.int and IN_CLOSE_WRITE) != 0:
mev.kind = MonitorCloseWrite
elif (event.mask.int and IN_CLOSE_NOWRITE) != 0:
mev.kind = MonitorCloseNoWrite
elif (event.mask.int and IN_CREATE) != 0: mev.kind = MonitorCreate
elif (event.mask.int and IN_DELETE) != 0:
mev.kind = MonitorDelete
elif (event.mask.int and IN_DELETE_SELF) != 0:
mev.kind = MonitorDeleteSelf
elif (event.mask.int and IN_MODIFY) != 0: mev.kind = MonitorModify
elif (event.mask.int and IN_MOVE_SELF) != 0:
mev.kind = MonitorMoveSelf
elif (event.mask.int and IN_OPEN) != 0: mev.kind = MonitorOpen
if mev.kind != MonitorMoved:
mev.fullname = ""
result.add(mev)
inc(i, sizeof(INotifyEvent) + event.len.int)
# If movedFrom events have not been matched with a moveTo. File has
# been moved to an unwatched location, emit a MonitorDelete.
for cookie, t in pairs(movedFrom):
var mev: MonitorEvent
mev.kind = MonitorDelete
mev.wd = t.wd
mev.name = t.old
result.add(mev)
proc FSMonitorRead(h: RootRef) =
var events = FSMonitor(h).getEvent(FSMonitor(h).fd)
#var newEv: MonitorEvent
for ev in events:
var target = FSMonitor(h).targets[ev.wd]
var newEv = ev
if newEv.kind == MonitorMoved:
newEv.oldPath = target / newEv.oldPath
newEv.newPath = target / newEv.name
else:
newEv.fullName = target / newEv.name
FSMonitor(h).handleEvent(FSMonitor(h), newEv)
proc toDelegate(m: FSMonitor): Delegate =
result = newDelegate()
result.deleVal = m
result.fd = (type(result.fd))(m.fd)
result.mode = fmRead
result.handleRead = FSMonitorRead
result.open = true
proc register*(d: Dispatcher, monitor: FSMonitor,
handleEvent: proc (m: FSMonitor, ev: MonitorEvent) {.closure.}) =
## Registers ``monitor`` with dispatcher ``d``.
monitor.handleEvent = handleEvent
var deleg = toDelegate(monitor)
d.register(deleg)
when not defined(testing) and isMainModule:
proc main =
var
disp = newDispatcher()
monitor = newMonitor()
n = 0
n = monitor.add("/tmp")
assert n == 1
n = monitor.add("/tmp", {MonitorAll})
assert n == 1
n = monitor.add("/tmp", {MonitorCloseWrite, MonitorCloseNoWrite})
assert n == 1
n = monitor.add("/tmp", {MonitorMoved, MonitorOpen, MonitorAccess})
assert n == 1
disp.register(monitor,
proc (m: FSMonitor, ev: MonitorEvent) =
echo("Got event: ", ev.kind)
if ev.kind == MonitorMoved:
echo("From ", ev.oldPath, " to ", ev.newPath)
echo("Name is ", ev.name)
else:
echo("Name ", ev.name, " fullname ", ev.fullName))
while true:
if not disp.poll(): break
main()
|