OLD | NEW |
| (Empty) |
1 | |
2 import fcntl, signal, os | |
3 | |
4 class DNotify_Handler: | |
5 def __init__(self): | |
6 self.watchers = {} | |
7 self.installed = 0 | |
8 def install(self): | |
9 if self.installed: | |
10 return | |
11 signal.signal(signal.SIGIO, self.fire) | |
12 self.installed = 1 | |
13 def uninstall(self): | |
14 if not self.installed: | |
15 return | |
16 signal.signal(signal.SIGIO, signal.SIG_DFL) | |
17 self.installed = 0 | |
18 def add(self, watcher): | |
19 self.watchers[watcher.fd] = watcher | |
20 self.install() | |
21 def remove(self, watcher): | |
22 if self.watchers.has_key(watcher.fd): | |
23 del(self.watchers[watcher.fd]) | |
24 if not self.watchers: | |
25 self.uninstall() | |
26 def fire(self, signum, frame): | |
27 # this is the signal handler | |
28 # without siginfo_t, we must fire them all | |
29 for watcher in self.watchers.values(): | |
30 watcher.callback() | |
31 | |
32 class DNotify: | |
33 DN_ACCESS = fcntl.DN_ACCESS # a file in the directory was read | |
34 DN_MODIFY = fcntl.DN_MODIFY # a file was modified (write,truncate) | |
35 DN_CREATE = fcntl.DN_CREATE # a file was created | |
36 DN_DELETE = fcntl.DN_DELETE # a file was unlinked | |
37 DN_RENAME = fcntl.DN_RENAME # a file was renamed | |
38 DN_ATTRIB = fcntl.DN_ATTRIB # a file had attributes changed (chmod,chown) | |
39 | |
40 handler = [None] | |
41 | |
42 def __init__(self, dirname, callback=None, | |
43 flags=[DN_MODIFY,DN_CREATE,DN_DELETE,DN_RENAME]): | |
44 | |
45 """This object watches a directory for changes. The .callback | |
46 attribute should be set to a function to be run every time something | |
47 happens to it. Be aware that it will be called more times than you | |
48 expect.""" | |
49 | |
50 if callback: | |
51 self.callback = callback | |
52 else: | |
53 self.callback = self.fire | |
54 self.dirname = dirname | |
55 self.flags = reduce(lambda x, y: x | y, flags) | fcntl.DN_MULTISHOT | |
56 self.fd = os.open(dirname, os.O_RDONLY) | |
57 # ideally we would move the notification to something like SIGRTMIN, | |
58 # (to free up SIGIO) and use sigaction to have the signal handler | |
59 # receive a structure with the fd number. But python doesn't offer | |
60 # either. | |
61 if not self.handler[0]: | |
62 self.handler[0] = DNotify_Handler() | |
63 self.handler[0].add(self) | |
64 fcntl.fcntl(self.fd, fcntl.F_NOTIFY, self.flags) | |
65 def remove(self): | |
66 self.handler[0].remove(self) | |
67 os.close(self.fd) | |
68 def fire(self): | |
69 print self.dirname, "changed!" | |
70 | |
71 def test_dnotify1(): | |
72 d = DNotify(".") | |
73 while 1: | |
74 signal.pause() | |
75 | |
76 def test_dnotify2(): | |
77 # create ./foo/, create/delete files in ./ and ./foo/ while this is | |
78 # running. Notice how both notifiers are fired when anything changes; | |
79 # this is an unfortunate side-effect of the lack of extended sigaction | |
80 # support in Python. | |
81 count = [0] | |
82 d1 = DNotify(".") | |
83 def fire1(count=count, d1=d1): | |
84 print "./ changed!", count[0] | |
85 count[0] += 1 | |
86 if count[0] > 5: | |
87 d1.remove() | |
88 del(d1) | |
89 # change the callback, since we can't define it until after we have the | |
90 # dnotify object. Hmm, unless we give the dnotify to the callback. | |
91 d1.callback = fire1 | |
92 def fire2(): print "foo/ changed!" | |
93 d2 = DNotify("foo", fire2) | |
94 while 1: | |
95 signal.pause() | |
96 | |
97 | |
98 if __name__ == '__main__': | |
99 test_dnotify2() | |
100 | |
OLD | NEW |