1
2
3
4
5
6
7 import os
8 import struct
9
10 try:
11 import ctypes
12 except ImportError:
13 ctypes = None
14
15
16 from twisted.internet import reactor
17 from twisted.internet.abstract import FileDescriptor
18 from twisted.internet import fdesc, protocol
19
20 from twisted.python.filepath import FilePath
21
22
23
24
25 IN_ACCESS = 0x00000001L
26 IN_MODIFY = 0x00000002L
27 IN_ATTRIB = 0x00000004L
28 IN_CLOSE_WRITE = 0x00000008L
29 IN_CLOSE_NOWRITE = 0x00000010L
30 IN_OPEN = 0x00000020L
31 IN_MOVED_FROM = 0x00000040L
32 IN_MOVED_TO = 0x00000080L
33 IN_CREATE = 0x00000100L
34 IN_DELETE = 0x00000200L
35 IN_DELETE_SELF = 0x00000400L
36 IN_MOVE_SELF = 0x00000800L
37 IN_UNMOUNT = 0x00002000L
38 IN_Q_OVERFLOW = 0x00004000L
39 IN_IGNORED = 0x00008000L
40
41 IN_ONLYDIR = 0x01000000
42 IN_DONT_FOLLOW = 0x02000000
43 IN_MASK_ADD = 0x20000000
44 IN_ISDIR = 0x40000000
45 IN_ONESHOT = 0x80000000
46
47 IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
48 IN_MOVED = IN_MOVED_FROM | IN_MOVED_TO
49 IN_CHANGED = IN_MODIFY | IN_ATTRIB
50
51 IN_WATCH_MASK = IN_MODIFY | IN_ATTRIB | \
52 IN_CREATE | IN_DELETE | \
53 IN_DELETE_SELF | IN_MOVE_SELF | \
54 IN_UNMOUNT | IN_MOVED_FROM | IN_MOVED_TO
55
56
57 _flag_to_human = {
58 IN_ACCESS: 'access',
59 IN_MODIFY: 'modify',
60 IN_ATTRIB: 'attrib',
61 IN_CLOSE_WRITE: 'close_write',
62 IN_CLOSE_NOWRITE: 'close_nowrite',
63 IN_OPEN: 'open',
64 IN_MOVED_FROM: 'moved_from',
65 IN_MOVED_TO: 'moved_to',
66 IN_CREATE: 'create',
67 IN_DELETE: 'delete',
68 IN_DELETE_SELF: 'delete_self',
69 IN_MOVE_SELF: 'move_self',
70 IN_UNMOUNT: 'unmount',
71 IN_Q_OVERFLOW: 'queue_overflow',
72 IN_IGNORED: 'ignored',
73 IN_ONLYDIR: 'only_dir',
74 IN_DONT_FOLLOW: 'dont_follow',
75 IN_MASK_ADD: 'mask_add',
76 IN_ISDIR: 'is_dir',
77 IN_ONESHOT: 'one_shot'}
78
79
80
81 _inotify_syscalls = { 'i386': (291,292,293),
82 'i486': (291,292,293),
83 'i586': (291,292,293),
84 'i686': (291,292,293),
85 'x86_64': (253,254,255),
86 'armv6l':(316,317,318),
87 'armv5tej1':(316,317,318),
88 'ppc': (275,276,277),
89 }
90
91
93
95 self.path = path
96 self.mask = mask
97 self.auto_add = auto_add
98 self.callbacks = []
99 if callbacks != None:
100 if not isinstance(callbacks, list):
101 callbacks = [callbacks]
102 self.callbacks = callbacks
103
105 self.callbacks.append((callback,parameter))
106
108 try:
109 del self.callbacks[callback]
110 except:
111 pass
112
113 - def notify(self, filename, events):
114 for callback in self.callbacks:
115 if callback != None:
116 callback[0](self, filename, events, callback[1])
117
118 -class INotify(FileDescriptor, object):
119 _instance_ = None
120
121 - def __new__(cls, *args, **kwargs):
122 obj = getattr(cls,'_instance_',None)
123 if obj is not None:
124 return obj
125 else:
126
127 if ctypes == None:
128 raise SystemError, "ctypes not detected on this system, INotify support disabled"
129
130 obj = super(INotify, cls).__new__(cls, *args, **kwargs)
131 try:
132 obj.libc = ctypes.CDLL("libc.so.6")
133 except:
134 raise SystemError, "libc not found, INotify support disabled"
135
136 try:
137 obj.inotify_init = obj.libc.inotify_init
138
139 obj.inotify_add_watch = obj.libc_inotify_add_watch
140 obj.inotify_rm_watch = obj.libc_inotify_rm_watch
141 except:
142 import platform
143 machine = platform.machine()
144 try:
145 obj._init_syscall_id = _inotify_syscalls[machine][0]
146 obj._add_watch_syscall_id = _inotify_syscalls[machine][1]
147 obj._rm_watch_syscall_id = _inotify_syscalls[machine][2]
148 except:
149 raise SystemError, "unknown system '%s', INotify support disabled" % machine
150
151 FileDescriptor.__init__(obj)
152
153 obj._fd = obj.inotify_init()
154 if obj._fd < 0:
155 raise SystemError, "INotify support not detected on this system."
156
157 fdesc.setNonBlocking(obj._fd)
158
159 reactor.addReader(obj)
160
161 obj._buffer = ''
162 obj._watchpoints = {}
163 cls._instance_ = obj
164 return obj
165
167 reactor.removeReader(self)
168 if hasattr(self, '_fd') and self._fd >= 0:
169 try:
170 os.close(self._fd)
171 except OSError:
172 pass
173
174 if hasattr(INotify, '_instance_'):
175 del INotify._instance_
176
177 __del__ = release
178
180 return self.libc.syscall(self._init_syscall_id)
181
183 if type(path) is unicode:
184 path = path.encode('utf-8')
185 self.libc.syscall.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_int]
186 else:
187 self.libc.syscall.argtypes = None
188 return self.libc.syscall(self._add_watch_syscall_id, self._fd, path, mask)
189
191 return self.libc.syscall(self._rm_watch_syscall_id, self._fd, wd)
192
200
203
206
213
214 - def notify(self, iwp, filename, mask, parameter=None):
217
219 self._buffer += os.read(self._fd, 1024)
220
221 while True:
222 if len(self._buffer) < 16:
223 break
224
225 wd, mask, cookie, size = struct.unpack("=LLLL", self._buffer[0:16])
226 if size:
227 name = self._buffer[16:16+size].rstrip('\0')
228 else:
229 name = None
230
231 self._buffer = self._buffer[16+size:]
232
233 try:
234 iwp = self._watchpoints[wd]
235 except:
236 continue
237
238 path = iwp.path
239 if name:
240 name = unicode(name, 'utf-8')
241 path = os.path.join(path, name)
242
243 iwp.notify( name, mask)
244
245 if( iwp.auto_add and mask & IN_ISDIR and mask & IN_CREATE):
246 self.watch(path, mask = iwp.mask, auto_add = True, callbacks=iwp.callbacks)
247
248 if mask & IN_DELETE_SELF:
249
250 del self._watchpoints[wd]
251
252
253 - def watch(self, path, mask = IN_WATCH_MASK, auto_add = None, callbacks=[], recursive=False):
254 if recursive:
255 for root, dirs, files in os.walk(path):
256 self.watch(root, mask, auto_add, callbacks, False)
257 else:
258 if isinstance(path, FilePath):
259 path = path.path
260 path = os.path.realpath(path)
261 for wd, iwp in self._watchpoints.items():
262 if iwp.path == path:
263 return wd
264
265 mask = mask | IN_DELETE_SELF
266
267
268 wd = self.inotify_add_watch(path, mask)
269 if wd < 0:
270 raise IOError, "Failed to add watch on '%s' - (%r)" % (path.encode('ascii', 'ignore'),wd)
271
272 iwp = IWatchPoint(path, mask, auto_add, callbacks)
273 self._watchpoints[wd] = iwp
274
283
285 for wd, iwp in self._watchpoints.items():
286 if iwp.path == path:
287 return True
288 return False
289
290 if __name__ == '__main__':
291
292 i = INotify()
293 print i
294 i.watch(unicode('/tmp'), auto_add = True, callbacks=(i.notify,None), recursive=True)
295
296 i2 = INotify()
297 print i2
298 i2.watch('/', auto_add = True, callbacks=(i2.notify,None), recursive=False)
299 reactor.run()
300