Mercurial > libervia-backend
comparison src/plugins/plugin_misc_maildir.py @ 253:f45ffbf211e9
MAILDIR + IMAP plugins: first draft
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 17 Jan 2011 00:15:50 +0100 |
parents | |
children | 9fc32d1d9046 |
comparison
equal
deleted
inserted
replaced
252:c09aa319712e | 253:f45ffbf211e9 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 SAT plugin for managing imap server | |
6 Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) | |
7 | |
8 This program is free software: you can redistribute it and/or modify | |
9 it under the terms of the GNU General Public License as published by | |
10 the Free Software Foundation, either version 3 of the License, or | |
11 (at your option) any later version. | |
12 | |
13 This program is distributed in the hope that it will be useful, | |
14 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 GNU General Public License for more details. | |
17 | |
18 You should have received a copy of the GNU General Public License | |
19 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
20 """ | |
21 | |
22 from logging import debug, info, error | |
23 import warnings | |
24 warnings.filterwarnings('ignore','the MimeWriter',DeprecationWarning,'twisted' ) #FIXME: to be removed, see http://twistedmatrix.com/trac/ticket/4038 | |
25 from twisted.internet import protocol | |
26 from twisted.words.protocols.jabber import error as jab_error | |
27 from twisted.cred import portal,checkers | |
28 from twisted.mail import imap4,maildir | |
29 from email.parser import Parser | |
30 import email.message | |
31 from email.charset import Charset | |
32 import os,os.path | |
33 from cStringIO import StringIO | |
34 from twisted.internet import reactor | |
35 import pdb | |
36 | |
37 | |
38 from zope.interface import implements | |
39 | |
40 | |
41 PLUGIN_INFO = { | |
42 "name": "Maildir Plugin", | |
43 "import_name": "Maildir", | |
44 "type": "Misc", | |
45 "protocols": [], | |
46 "dependencies": [], | |
47 "main": "MaildirBox", | |
48 "handler": "no", | |
49 "description": _("""Intercept "normal" type messages, and put them in a Maildir type box""") | |
50 } | |
51 | |
52 MAILDIR_PATH = "Maildir" | |
53 | |
54 class MaildirError(Exception): | |
55 pass | |
56 | |
57 class MaildirBox(): | |
58 | |
59 def __init__(self, host): | |
60 info(_("Plugin Maildir initialization")) | |
61 self.host = host | |
62 | |
63 self.__observed={} | |
64 self.__mailboxes={} | |
65 | |
66 #the trigger | |
67 host.trigger.add("MessageReceived", self.MessageReceivedTrigger) | |
68 | |
69 def accessMessageBox(self, boxname, observer=None): | |
70 """Create and return a MailboxUser instance | |
71 @param boxname: name of the box | |
72 @param observer: method to call when a NewMessage arrive""" | |
73 if not self.__mailboxes.has_key(boxname): | |
74 self.__mailboxes[boxname]=MailboxUser(self, boxname, observer) | |
75 else: | |
76 if observer: | |
77 self.addObserver(observer, boxname) | |
78 return self.__mailboxes[boxname] | |
79 | |
80 def _removeBoxAccess(self, boxname, mailboxUser): | |
81 """Remove a reference to a box | |
82 @param name: name of the box | |
83 @param mailboxUser: MailboxUser instance""" | |
84 if not self.__mailboxes.has_key(boxname): | |
85 err_msg=_("Trying to remove an mailboxUser not referenced") | |
86 error(_("INTERNAL ERROR: ") + err_msg) | |
87 raise MaildirError(err_msg) | |
88 assert self.__mailboxes[boxname]==mailboxUser | |
89 del __mailboxes[boxname] | |
90 | |
91 def _checkBoxReference(self, boxname): | |
92 """Check if there is a reference on a box, and return it | |
93 @param boxname: name of the box to check | |
94 @return: MailboxUser instance or None""" | |
95 if self.__mailboxes.has_key(boxname): | |
96 return self.__mailboxes[boxname] | |
97 | |
98 | |
99 | |
100 def MessageReceivedTrigger(self, message): | |
101 """This trigger catch normal message and put the in the Maildir box. | |
102 If the message is not of "normal" type, do nothing | |
103 @param message: message xmlstrem | |
104 @return: False if it's a normal message, True else""" | |
105 for e in message.elements(): | |
106 if e.name == "body": | |
107 type = message['type'] if message.hasAttribute('type') else 'chat' #FIXME: check specs | |
108 if message['type'] != 'normal': | |
109 return True | |
110 self.accessMessageBox("INBOX").addMessage(message) | |
111 return False | |
112 | |
113 def addObserver(self, callback, boxname, signal="NEW_MESSAGE"): | |
114 """Add an observer for maildir box changes | |
115 @param callback: method to call when the the box is updated | |
116 @param boxname: name of the box to observe | |
117 @param signal: which signal is observed by the caller""" | |
118 if not self.__observed.has_key(boxname): | |
119 self.__observed[boxname]={} | |
120 if not self.__observed[boxname].has_key(signal): | |
121 self.__observed[boxname][signal]=set() | |
122 self.__observed[boxname][signal].add(callback) | |
123 | |
124 def removeObserver(self, callback, boxname, signal="NEW_MESSAGE"): | |
125 """Remove an observer of maildir box changes | |
126 @param callback: method to remove from obervers | |
127 @param boxname: name of the box which was observed | |
128 @param signal: which signal was observed by the caller""" | |
129 if not self.__observed.has_key(boxname): | |
130 err_msg=_("Trying to remove an observer for an inexistant mailbox") | |
131 error(_("INTERNAL ERROR: ") + err_msg) | |
132 raise MaildirError(err_msg) | |
133 if not self.__observed[boxname].has_key(signal): | |
134 err_msg=_("Trying to remove an inexistant observer, no observer for this signal") | |
135 error(_("INTERNAL ERROR: ") + err_msg) | |
136 raise MaildirError(err_msg) | |
137 if not callback in self.__observed[boxname][signal]: | |
138 err_msg=_("Trying to remove an inexistant observer") | |
139 error(_("INTERNAL ERROR: ") + err_msg) | |
140 raise MaildirError(err_msg) | |
141 self.__observed[boxname][signal].remove(callback) | |
142 | |
143 def emitSignal(self, boxname, signal_name): | |
144 """Emit the signal to observer""" | |
145 debug('emitSignal %s %s' %(boxname, signal_name)) | |
146 try: | |
147 for observer_cb in self.__observed[boxname][signal_name]: | |
148 observer_cb() | |
149 except KeyError: | |
150 pass | |
151 | |
152 | |
153 class MailboxUser: | |
154 """This class is used to access a mailbox""" | |
155 | |
156 def xmppMessage2mail(self, message): | |
157 """Convert the XMPP's XML message to a basic rfc2822 message | |
158 @param xml: domish.Element of the message | |
159 @return: string email""" | |
160 mail = email.message.Message() | |
161 mail['MIME-Version'] = "1.0" | |
162 mail['Content-Type'] = "text/plain; charset=UTF-8; format=flowed" | |
163 mail['Content-Transfer-Encoding'] = "8bit" | |
164 mail['From'] = message['from'].encode('utf-8') | |
165 mail['To'] = message['to'].encode('utf-8') | |
166 mail['Date'] = email.utils.formatdate().encode('utf-8') | |
167 #TODO: save thread id | |
168 for e in message.elements(): | |
169 if e.name == "body": | |
170 mail.set_payload(e.children[0].encode('utf-8')) | |
171 elif e.name == "subject": | |
172 mail['Subject'] = e.children[0].encode('utf-8') | |
173 return mail.as_string() | |
174 | |
175 def __init__(self, _maildir, name, observer=None): | |
176 """@param _maildir: the main MaildirBox instance | |
177 @param name: name of the mailbox | |
178 THIS OBJECT MUST NOT BE USED DIRECTLY: use MaildirBox.accessMessageBox instead""" | |
179 if _maildir._checkBoxReference(self): | |
180 error ("INTERNAL ERROR: MailboxUser MUST NOT be instancied directly") | |
181 raise MailboxException('double MailboxUser instanciation') | |
182 if name!="INBOX": | |
183 raise NotImplementedError | |
184 self.name=name | |
185 self.maildir=_maildir | |
186 mailbox_path = os.path.expanduser(os.path.join(self.maildir.host.get_const('local_dir'), MAILDIR_PATH)) | |
187 self.mailbox_path=mailbox_path | |
188 self.mailbox = maildir.MaildirMailbox(mailbox_path) | |
189 self.observer=observer | |
190 if observer: | |
191 debug("adding observer for %s" % name) | |
192 self.maildir.addObserver(observer, name, "NEW_MESSAGE") | |
193 | |
194 def __destroy__(self): | |
195 if observer: | |
196 debug("removing observer for %s" % self.name) | |
197 self._maildir.removeObserver(observer, self.name, "NEW_MESSAGE") | |
198 self._maildir._removeBoxAccess(self.name, self) | |
199 | |
200 def addMessage(self, message): | |
201 """Add a message to the box | |
202 @param message: XMPP XML message""" | |
203 self.mailbox.appendMessage(self.xmppMessage2mail(message)).addCallback(self.emitSignal, "NEW_MESSAGE") | |
204 | |
205 def emitSignal(self, ignore, signal): | |
206 """Emit the signal to the observers""" | |
207 print ('self: %s, mailbox: %s, count: %i' % (self, self.mailbox, self.getMessageCount())) | |
208 self.maildir.emitSignal(self.name, signal) | |
209 | |
210 def getId(self, mess_idx): | |
211 """Return the Unique ID of the message | |
212 @mess_idx: message index""" | |
213 return self.mailbox.getUidl(mess_idx) | |
214 | |
215 def getMessageCount(self): | |
216 """Return number of mails present in this box""" | |
217 print "count: %i" % len(self.mailbox.listMessages()) | |
218 return len(self.mailbox.listMessages()) | |
219 | |
220 def getMessage(self, mess_idx): | |
221 """Return the full message | |
222 @mess_idx: message index""" | |
223 return self.mailbox.getMessage(mess_idx) |