| Trees | Indices | Help | 
        
  | 
  
|---|
| 
       | 
  
  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") 
 34   
 36      """I watch a maildir for new messages. I should be placed as the service 
 37      child of some MultiService instance. When running, I use the linux 
 38      dirwatcher API (if available) or poll for new files in the 'new' 
 39      subdirectory of my maildir path. When I discover a new message, I invoke 
 40      my .messageReceived() method with the short filename of the new message, 
 41      so the full name of the new file can be obtained with 
 42      os.path.join(maildir, 'new', filename). messageReceived() should be 
 43      overridden by a subclass to do something useful. I will not move or 
 44      delete the file on my own: the subclass's messageReceived() should 
 45      probably do that. 
 46      """ 
 47      pollinterval = 10  # only used if we don't have DNotify 
 48   
 50          """Create the Maildir watcher. BASEDIR is the maildir directory (the 
 51          one which contains new/ and tmp/) 
 52          """ 
 53          service.MultiService.__init__(self) 
 54          if basedir: 
 55              self.setBasedir(basedir) 
 56          self.files = [] 
 57          self.dnotify = None 
 58   
 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          self.newdir = os.path.join(self.basedir, "new") 
 66          self.curdir = os.path.join(self.basedir, "cur") 
 67   
 69          service.MultiService.startService(self) 
 70          if not os.path.isdir(self.newdir) or not os.path.isdir(self.curdir): 
 71              raise NoSuchMaildir("invalid maildir '%s'" % self.basedir) 
 72          try: 
 73              if dnotify: 
 74                  # we must hold an fd open on the directory, so we can get 
 75                  # notified when it changes. 
 76                  self.dnotify = dnotify.DNotify(self.newdir, 
 77                                                 self.dnotify_callback, 
 78                                                 [dnotify.DNotify.DN_CREATE]) 
 79          except (IOError, OverflowError): 
 80              # IOError is probably linux<2.4.19, which doesn't support 
 81              # dnotify. OverflowError will occur on some 64-bit machines 
 82              # because of a python bug 
 83              log.msg("DNotify failed, falling back to polling") 
 84          if not self.dnotify: 
 85              t = internet.TimerService(self.pollinterval, self.poll) 
 86              t.setServiceParent(self) 
 87          self.poll() 
 88   
 90          log.msg("dnotify noticed something, now polling") 
 91   
 92          # give it a moment. I found that qmail had problems when the message 
 93          # was removed from the maildir instantly. It shouldn't, that's what 
 94          # maildirs are made for. I wasn't able to eyeball any reason for the 
 95          # problem, and safecat didn't behave the same way, but qmail reports 
 96          # "Temporary_error_on_maildir_delivery" (qmail-local.c:165, 
 97          # maildir_child() process exited with rc not in 0,2,3,4). Not sure 
 98          # why, and I'd have to hack qmail to investigate further, so it's 
 99          # easier to just wait a second before yanking the message out of new/ 
100   
101          reactor.callLater(0.1, self.poll) 
102   
103   
105          if self.dnotify: 
106              self.dnotify.remove() 
107              self.dnotify = None 
108          return service.MultiService.stopService(self) 
109   
110      @defer.deferredGenerator 
112          assert self.basedir 
113          # see what's new 
114          for f in self.files: 
115              if not os.path.isfile(os.path.join(self.newdir, f)): 
116                  self.files.remove(f) 
117          newfiles = [] 
118          for f in os.listdir(self.newdir): 
119              if not f in self.files: 
120                  newfiles.append(f) 
121          self.files.extend(newfiles) 
122          for n in newfiles: 
123              try: 
124                  wfd = defer.waitForDeferred(self.messageReceived(n)) 
125                  yield wfd 
126                  wfd.getResult() 
127              except: 
128                  log.msg("while reading '%s' from maildir '%s':" % (n, self.basedir)) 
129                  log.err() 
130   
132          """ 
133          Call this from messageReceived to start processing the message; this 
134          moves the message file to the 'cur' directory and returns an open file 
135          handle for it. 
136   
137          @param filename: unqualified filename of the message 
138          @returns: open file 
139          """ 
140          if runtime.platformType == "posix": 
141              # open the file before moving it, because I'm afraid that once 
142              # it's in cur/, someone might delete it at any moment 
143              path = os.path.join(self.newdir, filename) 
144              f = open(path, "r") 
145              os.rename(os.path.join(self.newdir, filename), 
146                        os.path.join(self.curdir, filename)) 
147          elif runtime.platformType == "win32": 
148              # do this backwards under windows, because you can't move a file 
149              # that somebody is holding open. This was causing a Permission 
150              # Denied error on bear's win32-twisted1.3 buildslave. 
151              os.rename(os.path.join(self.newdir, filename), 
152                        os.path.join(self.curdir, filename)) 
153              path = os.path.join(self.curdir, filename) 
154              f = open(path, "r") 
155   
156          return f 
157   
162   
| Trees | Indices | Help | 
        
  | 
  
|---|
| Generated by Epydoc 3.0.1 on Sun Jul 17 13:45:34 2011 | http://epydoc.sourceforge.net |