Mercurial > libervia-backend
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_misc_maildir.py Mon Jan 17 00:15:50 2011 +0100 @@ -0,0 +1,223 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +SAT plugin for managing imap server +Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + +from logging import debug, info, error +import warnings +warnings.filterwarnings('ignore','the MimeWriter',DeprecationWarning,'twisted' ) #FIXME: to be removed, see http://twistedmatrix.com/trac/ticket/4038 +from twisted.internet import protocol +from twisted.words.protocols.jabber import error as jab_error +from twisted.cred import portal,checkers +from twisted.mail import imap4,maildir +from email.parser import Parser +import email.message +from email.charset import Charset +import os,os.path +from cStringIO import StringIO +from twisted.internet import reactor +import pdb + + +from zope.interface import implements + + +PLUGIN_INFO = { +"name": "Maildir Plugin", +"import_name": "Maildir", +"type": "Misc", +"protocols": [], +"dependencies": [], +"main": "MaildirBox", +"handler": "no", +"description": _("""Intercept "normal" type messages, and put them in a Maildir type box""") +} + +MAILDIR_PATH = "Maildir" + +class MaildirError(Exception): + pass + +class MaildirBox(): + + def __init__(self, host): + info(_("Plugin Maildir initialization")) + self.host = host + + self.__observed={} + self.__mailboxes={} + + #the trigger + host.trigger.add("MessageReceived", self.MessageReceivedTrigger) + + def accessMessageBox(self, boxname, observer=None): + """Create and return a MailboxUser instance + @param boxname: name of the box + @param observer: method to call when a NewMessage arrive""" + if not self.__mailboxes.has_key(boxname): + self.__mailboxes[boxname]=MailboxUser(self, boxname, observer) + else: + if observer: + self.addObserver(observer, boxname) + return self.__mailboxes[boxname] + + def _removeBoxAccess(self, boxname, mailboxUser): + """Remove a reference to a box + @param name: name of the box + @param mailboxUser: MailboxUser instance""" + if not self.__mailboxes.has_key(boxname): + err_msg=_("Trying to remove an mailboxUser not referenced") + error(_("INTERNAL ERROR: ") + err_msg) + raise MaildirError(err_msg) + assert self.__mailboxes[boxname]==mailboxUser + del __mailboxes[boxname] + + def _checkBoxReference(self, boxname): + """Check if there is a reference on a box, and return it + @param boxname: name of the box to check + @return: MailboxUser instance or None""" + if self.__mailboxes.has_key(boxname): + return self.__mailboxes[boxname] + + + + def MessageReceivedTrigger(self, message): + """This trigger catch normal message and put the in the Maildir box. + If the message is not of "normal" type, do nothing + @param message: message xmlstrem + @return: False if it's a normal message, True else""" + for e in message.elements(): + if e.name == "body": + type = message['type'] if message.hasAttribute('type') else 'chat' #FIXME: check specs + if message['type'] != 'normal': + return True + self.accessMessageBox("INBOX").addMessage(message) + return False + + def addObserver(self, callback, boxname, signal="NEW_MESSAGE"): + """Add an observer for maildir box changes + @param callback: method to call when the the box is updated + @param boxname: name of the box to observe + @param signal: which signal is observed by the caller""" + if not self.__observed.has_key(boxname): + self.__observed[boxname]={} + if not self.__observed[boxname].has_key(signal): + self.__observed[boxname][signal]=set() + self.__observed[boxname][signal].add(callback) + + def removeObserver(self, callback, boxname, signal="NEW_MESSAGE"): + """Remove an observer of maildir box changes + @param callback: method to remove from obervers + @param boxname: name of the box which was observed + @param signal: which signal was observed by the caller""" + if not self.__observed.has_key(boxname): + err_msg=_("Trying to remove an observer for an inexistant mailbox") + error(_("INTERNAL ERROR: ") + err_msg) + raise MaildirError(err_msg) + if not self.__observed[boxname].has_key(signal): + err_msg=_("Trying to remove an inexistant observer, no observer for this signal") + error(_("INTERNAL ERROR: ") + err_msg) + raise MaildirError(err_msg) + if not callback in self.__observed[boxname][signal]: + err_msg=_("Trying to remove an inexistant observer") + error(_("INTERNAL ERROR: ") + err_msg) + raise MaildirError(err_msg) + self.__observed[boxname][signal].remove(callback) + + def emitSignal(self, boxname, signal_name): + """Emit the signal to observer""" + debug('emitSignal %s %s' %(boxname, signal_name)) + try: + for observer_cb in self.__observed[boxname][signal_name]: + observer_cb() + except KeyError: + pass + + +class MailboxUser: + """This class is used to access a mailbox""" + + def xmppMessage2mail(self, message): + """Convert the XMPP's XML message to a basic rfc2822 message + @param xml: domish.Element of the message + @return: string email""" + mail = email.message.Message() + mail['MIME-Version'] = "1.0" + mail['Content-Type'] = "text/plain; charset=UTF-8; format=flowed" + mail['Content-Transfer-Encoding'] = "8bit" + mail['From'] = message['from'].encode('utf-8') + mail['To'] = message['to'].encode('utf-8') + mail['Date'] = email.utils.formatdate().encode('utf-8') + #TODO: save thread id + for e in message.elements(): + if e.name == "body": + mail.set_payload(e.children[0].encode('utf-8')) + elif e.name == "subject": + mail['Subject'] = e.children[0].encode('utf-8') + return mail.as_string() + + def __init__(self, _maildir, name, observer=None): + """@param _maildir: the main MaildirBox instance + @param name: name of the mailbox + THIS OBJECT MUST NOT BE USED DIRECTLY: use MaildirBox.accessMessageBox instead""" + if _maildir._checkBoxReference(self): + error ("INTERNAL ERROR: MailboxUser MUST NOT be instancied directly") + raise MailboxException('double MailboxUser instanciation') + if name!="INBOX": + raise NotImplementedError + self.name=name + self.maildir=_maildir + mailbox_path = os.path.expanduser(os.path.join(self.maildir.host.get_const('local_dir'), MAILDIR_PATH)) + self.mailbox_path=mailbox_path + self.mailbox = maildir.MaildirMailbox(mailbox_path) + self.observer=observer + if observer: + debug("adding observer for %s" % name) + self.maildir.addObserver(observer, name, "NEW_MESSAGE") + + def __destroy__(self): + if observer: + debug("removing observer for %s" % self.name) + self._maildir.removeObserver(observer, self.name, "NEW_MESSAGE") + self._maildir._removeBoxAccess(self.name, self) + + def addMessage(self, message): + """Add a message to the box + @param message: XMPP XML message""" + self.mailbox.appendMessage(self.xmppMessage2mail(message)).addCallback(self.emitSignal, "NEW_MESSAGE") + + def emitSignal(self, ignore, signal): + """Emit the signal to the observers""" + print ('self: %s, mailbox: %s, count: %i' % (self, self.mailbox, self.getMessageCount())) + self.maildir.emitSignal(self.name, signal) + + def getId(self, mess_idx): + """Return the Unique ID of the message + @mess_idx: message index""" + return self.mailbox.getUidl(mess_idx) + + def getMessageCount(self): + """Return number of mails present in this box""" + print "count: %i" % len(self.mailbox.listMessages()) + return len(self.mailbox.listMessages()) + + def getMessage(self, mess_idx): + """Return the full message + @mess_idx: message index""" + return self.mailbox.getMessage(mess_idx)