Mercurial > libervia-backend
diff src/plugins/plugin_misc_imap.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_imap.py Mon Jan 17 00:15:50 2011 +0100 @@ -0,0 +1,373 @@ +#!/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 +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 +from email.parser import Parser +import email.message +import os,os.path +from cStringIO import StringIO +from twisted.internet import reactor +import pdb + + +from zope.interface import implements + + +PLUGIN_INFO = { +"name": "IMAP server Plugin", +"import_name": "IMAP", +"type": "Misc", +"protocols": [], +"dependencies": ["Maildir"], +"main": "IMAP_server", +"handler": "no", +"description": _("""Create an Imap server that you can use to read your "normal" type messages""") +} + +class IMAP_server(): + + params = """ + <params> + <general> + <category name="IMAP Server"> + <param name="Port" value="10143" type="string" /> + </category> + </general> + </params> + """ + + def __init__(self, host): + info(_("Plugin Imap Server initialization")) + self.host = host + + #parameters + host.memory.importParams(self.params) + + port = int(self.host.memory.getParamA("Port", "IMAP Server")) + info(_("Launching IMAP server on port %d"), port) + + self.server_factory = ImapServerFactory(self.host) + reactor.listenTCP(port, self.server_factory) + +class Message(): + implements(imap4.IMessage) + + def __init__(self, uid, mess_fp): + debug('Message Init') + self.uid=uid + self.mess_fp=mess_fp + self.message=Parser().parse(mess_fp) + + def getUID(self): + """Retrieve the unique identifier associated with this message. + """ + debug('getUID (message)') + return self.uid + + def getFlags(self): + """Retrieve the flags associated with this message. + @return: The flags, represented as strings. + """ + debug('getFlags') + return [] + + def getInternalDate(self): + """Retrieve the date internally associated with this message. + @return: An RFC822-formatted date string. + """ + debug('getInternalDate') + return self.message['Date'] + + + def getHeaders(self, negate, *names): + """Retrieve a group of message headers. + @param names: The names of the headers to retrieve or omit. + @param negate: If True, indicates that the headers listed in names + should be omitted from the return value, rather than included. + @return: A mapping of header field names to header field values + """ + debug('getHeaders %s - %s' % (negate, names)) + final_dict={} + to_check=[name.lower() for name in names] + for header in self.message.keys(): + if (negate and not header.lower() in to_check) or \ + (not negate and header.lower() in to_check): + final_dict[header]=self.message[header] + return final_dict + + def getBodyFile(self): + """Retrieve a file object containing only the body of this message. + """ + debug('getBodyFile') + return StringIO(self.message.get_payload()) + + def getSize(self): + """Retrieve the total size, in octets, of this message. + """ + debug('getSize') + self.mess_fp.seek(0,os.SEEK_END) + return self.mess_fp.tell() + + + def isMultipart(self): + """Indicate whether this message has subparts. + """ + debug('isMultipart') + return False + + def getSubPart(self,part): + """Retrieve a MIME sub-message + @param part: The number of the part to retrieve, indexed from 0. + @return: The specified sub-part. + """ + debug('getSubPart') + return TypeError + + +class SatMailbox: + implements(imap4.IMailbox) + + def __init__(self,host,name): + self.host = host + self.listeners=set() + debug ('Mailbox init (%s)', name) + if name!="INBOX": + raise imap4.MailboxException("Only INBOX is managed for the moment") + self.name=name + self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage) + + def newMessage(self): + """Called when a new message is in the mailbox""" + debug ("newMessage signal received") + nb_messages=self.getMessageCount() + for listener in self.listeners: + listener.newMessages(nb_messages,None) + + def getUIDValidity(self): + """Return the unique validity identifier for this mailbox. + """ + debug ('getUIDValidity') + return 0 + + def getUIDNext(self): + """Return the likely UID for the next message added to this mailbox. + """ + debug ('getUIDNext') + return self.getMessageCount()+1 + + def getUID(self,message): + """Return the UID of a message in the mailbox + @param message: The message sequence number + @return: The UID of the message. + """ + debug ('getUID') + return self.mailbox.getId(message) + + def getMessageCount(self): + """Return the number of messages in this mailbox. + """ + debug('getMessageCount') + ret = self.mailbox.getMessageCount() + debug("count = %i" % ret) + return ret + + def getRecentCount(self): + """Return the number of messages with the 'Recent' flag. + """ + debug('getRecentCount') + return 0 + + def getUnseenCount(self): + """Return the number of messages with the 'Unseen' flag. + """ + debug('getUnseenCount') + return 1 + + def isWriteable(self): + """Get the read/write status of the mailbox. + @return: A true value if write permission is allowed, a false value otherwise. + """ + debug('isWriteable') + return True + + def destroy(self): + """Called before this mailbox is deleted, permanently. + """ + debug('destroy') + + + def requestStatus(self, names): + """Return status information about this mailbox. + @param names: The status names to return information regarding. + The possible values for each name are: MESSAGES, RECENT, UIDNEXT, + UIDVALIDITY, UNSEEN. + @return: A dictionary containing status information about the + requested names is returned. If the process of looking this + information up would be costly, a deferred whose callback will + eventually be passed this dictionary is returned instead. + """ + debug('requestStatus') + return imap4.statusRequestHelper(self, names) + + def addListener(self, listener): + """Add a mailbox change listener + + @type listener: Any object which implements C{IMailboxListener} + @param listener: An object to add to the set of those which will + be notified when the contents of this mailbox change. + """ + debug('addListener %s' % listener) + self.listeners.add(listener) + + def removeListener(self, listener): + """Remove a mailbox change listener + + @type listener: Any object previously added to and not removed from + this mailbox as a listener. + @param listener: The object to remove from the set of listeners. + + @raise ValueError: Raised when the given object is not a listener for + this mailbox. + """ + debug('removeListener') + if listener in self.listeners: + self.listeners.remove(listener) + else: + raise imap4.MailboxException('Trying to remove an unknown listener') + + def addMessage(self, message, flags = (), date = None): + """Add the given message to this mailbox. + @param message: The RFC822 formatted message + @param flags: The flags to associate with this message + @param date: If specified, the date to associate with this + @return: A deferred whose callback is invoked with the message + id if the message is added successfully and whose errback is + invoked otherwise. + """ + debug('addMessage') + raise NotImplementedError + + def expunge(self): + """Remove all messages flagged \\Deleted. + @return: The list of message sequence numbers which were deleted, + or a Deferred whose callback will be invoked with such a list. + """ + debug('expunge') + raise NotImplementedError + + def fetch(self, messages, uid): + """Retrieve one or more messages. + @param messages: The identifiers of messages to retrieve information + about + @param uid: If true, the IDs specified in the query are UIDs; + """ + debug('fetch (%s, %s)'%(messages,uid)) + messages.last = self.getMessageCount() + for mes_idx in messages: + if mes_idx>self.getMessageCount(): + continue + yield (mes_idx,Message(mes_idx,self.mailbox.getMessage(mes_idx-1))) + + def store(self, messages, flags, mode, uid): + """Set the flags of one or more messages. + @param messages: The identifiers of the messages to set the flags of. + @param flags: The flags to set, unset, or add. + @param mode: If mode is -1, these flags should be removed from the + specified messages. If mode is 1, these flags should be added to + the specified messages. If mode is 0, all existing flags should be + cleared and these flags should be added. + @param uid: If true, the IDs specified in the query are UIDs; + otherwise they are message sequence IDs. + @return: A dict mapping message sequence numbers to sequences of str + representing the flags set on the message after this operation has + been performed, or a Deferred whose callback will be invoked with + such a dict. + """ + debug('store') + raise NotImplementedError + + def getFlags(self): + """Return the flags defined in this mailbox + Flags with the \\ prefix are reserved for use as system flags. + @return: A list of the flags that can be set on messages in this mailbox. + """ + debug('getFlags') + #return ['\Seen','\Answered','\Flagged','\Deleted','\Draft', '\Recent'] + return [] + + def getHierarchicalDelimiter(self): + """Get the character which delimits namespaces for in this mailbox. + """ + debug('getHierarchicalDelimiter') + return '.' + + + +class ImapAccount(imap4.MemoryAccount): + #implements(imap4.IAccount) + # Actually implement the interface here + + def __init__(self, host, name): + debug("ImapAccount init") + self.host=host + imap4.MemoryAccount.__init__(self,name) + self.addMailbox("Inbox") #We only manage Inbox for the moment + debug ('INBOX added') + + def _emptyMailbox(self, name, id): + return SatMailbox(self.host,name) + + +class ImapRealm: + implements(portal.IRealm) + + def __init__(self,host): + self.host = host + + def requestAvatar(self, avatarID, mind, *interfaces): + debug('requestAvatar') + if imap4.IAccount not in interfaces: + raise NotImplementedError + return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None + +class ImapServerFactory(protocol.ServerFactory): + protocol = imap4.IMAP4Server + + def __init__(self, host): + self.host=host + + def startedConnecting(self, connector): + debug (_("IMAP server connection started")) + + def clientConnectionLost(self, connector, reason): + debug (_("IMAP server connection lost (reason: %s)"), reason) + + def buildProtocol(self, addr): + debug ("Building protocole") + prot = protocol.ServerFactory.buildProtocol(self, addr) + prot.portal = portal.Portal(ImapRealm(self.host)) + prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto")) + return prot