Welcome, guest | Sign In | My Account | Store | Cart

Inspired by another snippet here that watches a directory... Here is a directory watcher that only works on Linux, as it uses the asyncronous directory notify feature. It will generate a SIGIO on directory change. You should define a signal handler that calls this back (actaully, calls the instance).

Python, 49 lines
 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
################################################################
# DirectoryNotifier encapslates the code required for watching directory entry
# contents. Subclass this and override the entry_added() and entry_removed()
# methods. then, instantiate it with a directory name, and register it with the
# sighandler instance.  NOTE: this only works with Python 2.2 or greater on Linux. 
# Example:
#	dn = DirectoryNotifier(os.environ["HOME"])
#	manager.register(dn)

class DirectoryNotifier:
	def __init__(self, dirname):
		if not os.path.isdir(dirname):
			raise RuntimeError, "you can only watch a directory."
		self.dirname = dirname
		self.fd = os.open(dirname, 0)
		self.currentcontents = os.listdir(dirname)
		self.oldsig = fcntl.fcntl(self.fd, fcntl.F_GETSIG)
		fcntl.fcntl(self.fd, fcntl.F_SETSIG, 0)
		fcntl.fcntl(self.fd, fcntl.F_NOTIFY, fcntl.DN_DELETE|fcntl.DN_CREATE|fcntl.DN_MULTISHOT)

	def __del__(self):
#		fcntl.fcntl(self.fd, fcntl.F_SETSIG, self.oldsig)
		os.close(self.fd)
	
	def __str__(self):
		return "%s watching %s" % (self.__class__.__name__, self.dirname)

	# there are lots of race conditions here, but we'll live with that for now.
	def __call__(self, frame):
		newcontents = os.listdir(self.dirname)
		if len(newcontents) > len(self.currentcontents):
			new = filter(lambda item: item not in self.currentcontents, newcontents)
			self.entry_added(new)
		elif len(newcontents) < len(self.currentcontents):
			rem = filter(lambda item: item not in newcontents, self.currentcontents)
			self.entry_removed(rem)
		else:
			self.no_change()
		self.currentcontents = newcontents

	# override these in a subclass
	def entry_added(self, added):
		print added, "added to", self.dirname

	def entry_removed(self, removed):
		print removed, "removed from", self.dirname

	def no_change(self):
		print "No change in", self.dirname

1 comment

Bill Anderson 16 years, 5 months ago  # | flag

To use this recipe ...

import signal
import fcntl
import os

These are needed by the class.

Next, the "sighandler" he mentions is here: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/217830

Also note that the argument passed to the object when a directory's contents change is a list, not a single filename. If you want to perform some action on each file, you will need to iterate over the argument "added" or "removed".

Created by Keith Dart on Mon, 25 Aug 2003 (PSF)
Python recipes (4591)
Keith Dart's recipes (4)

Required Modules

  • (none specified)

Other Information and Tasks