1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
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
75
76 self.dnotify = dnotify.DNotify(self.newdir,
77 self.dnotify_callback,
78 [dnotify.DNotify.DN_CREATE])
79 except (IOError, OverflowError):
80
81
82
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
93
94
95
96
97
98
99
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
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
142
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
149
150
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
159 """Process a received message. The filename is relative to self.newdir.
160 Returns a Deferred."""
161 raise NotImplementedError
162