| Home | Trees | Indices | Help | 
        
  | 
  
|---|
| 
       | 
  
  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   
 20   
 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   
 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   
 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   
 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   
 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   
 89          if self.dnotify: 
 90              self.dnotify.remove() 
 91              self.dnotify = None 
 92          return service.MultiService.stopService(self) 
 93   
 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   
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   
| Home | Trees | Indices | Help | 
        
  | 
  
|---|
| Generated by Epydoc 3.0.1 on Thu Jan 21 21:26:32 2010 | http://epydoc.sourceforge.net |