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

Source Code for Module buildbot.util.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, runtime 
 24  from twisted.application import service, internet 
 25  from twisted.internet import reactor, defer 
 26  dnotify = None 
 27  try: 
 28      import dnotify 
 29  except: 
 30      log.msg("unable to import dnotify, so Maildir will use polling instead") 
31 32 -class NoSuchMaildir(Exception):
33 pass
34
35 -class MaildirService(service.MultiService):
36 pollinterval = 10 # only used if we don't have DNotify 37
38 - def __init__(self, basedir=None):
39 service.MultiService.__init__(self) 40 if basedir: 41 self.setBasedir(basedir) 42 self.files = [] 43 self.dnotify = None
44
45 - def setBasedir(self, basedir):
46 # some users of MaildirService (scheduler.Try_Jobdir, in particular) 47 # don't know their basedir until setServiceParent, since it is 48 # relative to the buildmaster's basedir. So let them set it late. We 49 # don't actually need it until our own startService. 50 self.basedir = basedir 51 self.newdir = os.path.join(self.basedir, "new") 52 self.curdir = os.path.join(self.basedir, "cur")
53
54 - def startService(self):
55 service.MultiService.startService(self) 56 if not os.path.isdir(self.newdir) or not os.path.isdir(self.curdir): 57 raise NoSuchMaildir("invalid maildir '%s'" % self.basedir) 58 try: 59 if dnotify: 60 # we must hold an fd open on the directory, so we can get 61 # notified when it changes. 62 self.dnotify = dnotify.DNotify(self.newdir, 63 self.dnotify_callback, 64 [dnotify.DNotify.DN_CREATE]) 65 except (IOError, OverflowError): 66 # IOError is probably linux<2.4.19, which doesn't support 67 # dnotify. OverflowError will occur on some 64-bit machines 68 # because of a python bug 69 log.msg("DNotify failed, falling back to polling") 70 if not self.dnotify: 71 t = internet.TimerService(self.pollinterval, self.poll) 72 t.setServiceParent(self) 73 self.poll()
74
75 - def dnotify_callback(self):
76 log.msg("dnotify noticed something, now polling") 77 78 # give it a moment. I found that qmail had problems when the message 79 # was removed from the maildir instantly. It shouldn't, that's what 80 # maildirs are made for. I wasn't able to eyeball any reason for the 81 # problem, and safecat didn't behave the same way, but qmail reports 82 # "Temporary_error_on_maildir_delivery" (qmail-local.c:165, 83 # maildir_child() process exited with rc not in 0,2,3,4). Not sure 84 # why, and I'd have to hack qmail to investigate further, so it's 85 # easier to just wait a second before yanking the message out of new/ 86 87 reactor.callLater(0.1, self.poll)
88 89
90 - def stopService(self):
91 if self.dnotify: 92 self.dnotify.remove() 93 self.dnotify = None 94 return service.MultiService.stopService(self)
95 96 @defer.inlineCallbacks
97 - def poll(self):
98 assert self.basedir 99 # see what's new 100 for f in self.files: 101 if not os.path.isfile(os.path.join(self.newdir, f)): 102 self.files.remove(f) 103 newfiles = [] 104 for f in os.listdir(self.newdir): 105 if not f in self.files: 106 newfiles.append(f) 107 self.files.extend(newfiles) 108 for n in newfiles: 109 try: 110 yield self.messageReceived(n) 111 except: 112 log.msg("while reading '%s' from maildir '%s':" % (n, self.basedir)) 113 log.err()
114
115 - def moveToCurDir(self, filename):
116 if runtime.platformType == "posix": 117 # open the file before moving it, because I'm afraid that once 118 # it's in cur/, someone might delete it at any moment 119 path = os.path.join(self.newdir, filename) 120 f = open(path, "r") 121 os.rename(os.path.join(self.newdir, filename), 122 os.path.join(self.curdir, filename)) 123 elif runtime.platformType == "win32": 124 # do this backwards under windows, because you can't move a file 125 # that somebody is holding open. This was causing a Permission 126 # Denied error on bear's win32-twisted1.3 buildslave. 127 os.rename(os.path.join(self.newdir, filename), 128 os.path.join(self.curdir, filename)) 129 path = os.path.join(self.curdir, filename) 130 f = open(path, "r") 131 132 return f
133
134 - def messageReceived(self, filename):
135 raise NotImplementedError
136