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

Source Code for Module buildbot.changes.maildir

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  # This is a class which watches a maildir for new messages. It uses the 
 18  # linux dirwatcher API (if available) to look for new files. The 
 19  # .messageReceived method is invoked with the filename of the new message, 
 20  # relative to the top of the maildir (so it will look like "new/blahblah"). 
 21   
 22  import os 
 23  from twisted.python import log 
 24  from twisted.application import service, internet 
 25  from twisted.internet import reactor 
 26  dnotify = None 
 27  try: 
 28      import dnotify 
 29  except: 
 30      # I'm not actually sure this log message gets recorded 
 31      log.msg("unable to import dnotify, so Maildir will use polling instead") 
 32   
33 -class NoSuchMaildir(Exception):
34 pass
35
36 -class MaildirService(service.MultiService):
37 """I watch a maildir for new messages. I should be placed as the service 38 child of some MultiService instance. When running, I use the linux 39 dirwatcher API (if available) or poll for new files in the 'new' 40 subdirectory of my maildir path. When I discover a new message, I invoke 41 my .messageReceived() method with the short filename of the new message, 42 so the full name of the new file can be obtained with 43 os.path.join(maildir, 'new', filename). messageReceived() should be 44 overridden by a subclass to do something useful. I will not move or 45 delete the file on my own: the subclass's messageReceived() should 46 probably do that. 47 """ 48 pollinterval = 10 # only used if we don't have DNotify 49
50 - def __init__(self, basedir=None):
51 """Create the Maildir watcher. BASEDIR is the maildir directory (the 52 one which contains new/ and tmp/) 53 """ 54 service.MultiService.__init__(self) 55 self.basedir = basedir 56 self.files = [] 57 self.dnotify = None
58
59 - def setBasedir(self, basedir):
60 # some users of MaildirService (scheduler.Try_Jobdir, in particular) 61 # don't know their basedir until setServiceParent, since it is 62 # relative to the buildmaster's basedir. So let them set it late. We 63 # don't actually need it until our own startService. 64 self.basedir = basedir
65
66 - def startService(self):
67 service.MultiService.startService(self) 68 self.newdir = os.path.join(self.basedir, "new") 69 if not os.path.isdir(self.basedir) or not os.path.isdir(self.newdir): 70 raise NoSuchMaildir("invalid maildir '%s'" % self.basedir) 71 try: 72 if dnotify: 73 # we must hold an fd open on the directory, so we can get 74 # notified when it changes. 75 self.dnotify = dnotify.DNotify(self.newdir, 76 self.dnotify_callback, 77 [dnotify.DNotify.DN_CREATE]) 78 except (IOError, OverflowError): 79 # IOError is probably linux<2.4.19, which doesn't support 80 # dnotify. OverflowError will occur on some 64-bit machines 81 # because of a python bug 82 log.msg("DNotify failed, falling back to polling") 83 if not self.dnotify: 84 t = internet.TimerService(self.pollinterval, self.poll) 85 t.setServiceParent(self) 86 self.poll()
87
88 - def dnotify_callback(self):
89 log.msg("dnotify noticed something, now polling") 90 91 # give it a moment. I found that qmail had problems when the message 92 # was removed from the maildir instantly. It shouldn't, that's what 93 # maildirs are made for. I wasn't able to eyeball any reason for the 94 # problem, and safecat didn't behave the same way, but qmail reports 95 # "Temporary_error_on_maildir_delivery" (qmail-local.c:165, 96 # maildir_child() process exited with rc not in 0,2,3,4). Not sure 97 # why, and I'd have to hack qmail to investigate further, so it's 98 # easier to just wait a second before yanking the message out of new/ 99 100 reactor.callLater(0.1, self.poll)
101 102
103 - def stopService(self):
104 if self.dnotify: 105 self.dnotify.remove() 106 self.dnotify = None 107 return service.MultiService.stopService(self)
108
109 - def poll(self):
110 assert self.basedir 111 # see what's new 112 for f in self.files: 113 if not os.path.isfile(os.path.join(self.newdir, f)): 114 self.files.remove(f) 115 newfiles = [] 116 for f in os.listdir(self.newdir): 117 if not f in self.files: 118 newfiles.append(f) 119 self.files.extend(newfiles) 120 # TODO: sort by ctime, then filename, since safecat uses a rather 121 # fine-grained timestamp in the filename 122 for n in newfiles: 123 # TODO: consider catching exceptions in messageReceived 124 self.messageReceived(n)
125
126 - def messageReceived(self, filename):
127 """Called when a new file is noticed. Will call 128 self.parent.messageReceived() with a path relative to maildir/new. 129 Should probably be overridden in subclasses.""" 130 self.parent.messageReceived(filename)
131