rename old extra_args in commands.py to svn_args to avoid confusion
[buildbot.git] / buildbot / changes / maildir.py
blob2e4a7069fe79b347526a209b627832230fa766a5
2 # This is a class which watches a maildir for new messages. It uses the
3 # linux dirwatcher API (if available) to look for new files. The
4 # .messageReceived method is invoked with the filename of the new message,
5 # relative to the top of the maildir (so it will look like "new/blahblah").
7 import os
8 from twisted.python import log
9 from twisted.application import service, internet
10 from twisted.internet import reactor
11 dnotify = None
12 try:
13 import dnotify
14 except:
15 # I'm not actually sure this log message gets recorded
16 log.msg("unable to import dnotify, so Maildir will use polling instead")
18 class NoSuchMaildir(Exception):
19 pass
21 class MaildirService(service.MultiService):
22 """I watch a maildir for new messages. I should be placed as the service
23 child of some MultiService instance. When running, I use the linux
24 dirwatcher API (if available) or poll for new files in the 'new'
25 subdirectory of my maildir path. When I discover a new message, I invoke
26 my .messageReceived() method with the short filename of the new message,
27 so the full name of the new file can be obtained with
28 os.path.join(maildir, 'new', filename). messageReceived() should be
29 overridden by a subclass to do something useful. I will not move or
30 delete the file on my own: the subclass's messageReceived() should
31 probably do that.
32 """
33 pollinterval = 10 # only used if we don't have DNotify
35 def __init__(self, basedir=None):
36 """Create the Maildir watcher. BASEDIR is the maildir directory (the
37 one which contains new/ and tmp/)
38 """
39 service.MultiService.__init__(self)
40 self.basedir = basedir
41 self.files = []
42 self.dnotify = None
44 def setBasedir(self, basedir):
45 # some users of MaildirService (scheduler.Try_Jobdir, in particular)
46 # don't know their basedir until setServiceParent, since it is
47 # relative to the buildmaster's basedir. So let them set it late. We
48 # don't actually need it until our own startService.
49 self.basedir = basedir
51 def startService(self):
52 service.MultiService.startService(self)
53 self.newdir = os.path.join(self.basedir, "new")
54 if not os.path.isdir(self.basedir) or not os.path.isdir(self.newdir):
55 raise NoSuchMaildir("invalid maildir '%s'" % self.basedir)
56 try:
57 if dnotify:
58 # we must hold an fd open on the directory, so we can get
59 # notified when it changes.
60 self.dnotify = dnotify.DNotify(self.newdir,
61 self.dnotify_callback,
62 [dnotify.DNotify.DN_CREATE])
63 except (IOError, OverflowError):
64 # IOError is probably linux<2.4.19, which doesn't support
65 # dnotify. OverflowError will occur on some 64-bit machines
66 # because of a python bug
67 log.msg("DNotify failed, falling back to polling")
68 if not self.dnotify:
69 t = internet.TimerService(self.pollinterval, self.poll)
70 t.setServiceParent(self)
71 self.poll()
73 def dnotify_callback(self):
74 log.msg("dnotify noticed something, now polling")
76 # give it a moment. I found that qmail had problems when the message
77 # was removed from the maildir instantly. It shouldn't, that's what
78 # maildirs are made for. I wasn't able to eyeball any reason for the
79 # problem, and safecat didn't behave the same way, but qmail reports
80 # "Temporary_error_on_maildir_delivery" (qmail-local.c:165,
81 # maildir_child() process exited with rc not in 0,2,3,4). Not sure
82 # why, and I'd have to hack qmail to investigate further, so it's
83 # easier to just wait a second before yanking the message out of new/
85 reactor.callLater(0.1, self.poll)
88 def stopService(self):
89 if self.dnotify:
90 self.dnotify.remove()
91 self.dnotify = None
92 return service.MultiService.stopService(self)
94 def poll(self):
95 assert self.basedir
96 # see what's new
97 for f in self.files:
98 if not os.path.isfile(os.path.join(self.newdir, f)):
99 self.files.remove(f)
100 newfiles = []
101 for f in os.listdir(self.newdir):
102 if not f in self.files:
103 newfiles.append(f)
104 self.files.extend(newfiles)
105 # TODO: sort by ctime, then filename, since safecat uses a rather
106 # fine-grained timestamp in the filename
107 for n in newfiles:
108 # TODO: consider catching exceptions in messageReceived
109 self.messageReceived(n)
111 def messageReceived(self, filename):
112 """Called when a new file is noticed. Will call
113 self.parent.messageReceived() with a path relative to maildir/new.
114 Should probably be overridden in subclasses."""
115 self.parent.messageReceived(filename)