Package buildbot :: Package changes :: Module maildir
[frames] | no frames]

Source Code for Module buildbot.changes.maildir

  1   
  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"). 
  6   
  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") 
 17   
18 -class NoSuchMaildir(Exception):
19 pass
20
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 34
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
43
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
50
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()
72
73 - def dnotify_callback(self):
74 log.msg("dnotify noticed something, now polling") 75 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/ 84 85 reactor.callLater(0.1, self.poll)
86 87
88 - def stopService(self):
89 if self.dnotify: 90 self.dnotify.remove() 91 self.dnotify = None 92 return service.MultiService.stopService(self)
93
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)
110
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)
116