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