MAILDIR + IMAP plugins: first draft
This commit is contained in:
parent
ab1f963c7e
commit
5d051810cf
1 changed files with 596 additions and 0 deletions
223
src/plugins/plugin_misc_maildir.py
Executable file
223
src/plugins/plugin_misc_maildir.py
Executable file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue