Mercurial > libervia-backend
diff src/plugins/plugin_misc_maildir.py @ 257:012c38b56cdd
plugin IMAP, plugin Maildir: profile management
- IMAP Login/pass is now checked against profile name/jabber pass
- Mailboxes are now per-profile
- flags are now checked without case sensitiveness
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 18 Jan 2011 00:57:26 +0100 |
parents | 55b750017b71 |
children | 11f71187d5e4 |
line wrap: on
line diff
--- a/src/plugins/plugin_misc_maildir.py Tue Jan 18 00:51:47 2011 +0100 +++ b/src/plugins/plugin_misc_maildir.py Tue Jan 18 00:57:26 2011 +0100 @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -SAT plugin for managing imap server +SàT plugin for managing Maildir type mail boxes Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) This program is free software: you can redistribute it and/or modify @@ -33,6 +33,7 @@ from cStringIO import StringIO from twisted.internet import reactor import pdb +from sat.exceptions import * from zope.interface import implements @@ -61,8 +62,9 @@ self.host = host self.__observed={} - self.__mailboxes={} - self.data=host.memory.getPrivate("MAILDIR_data") or {"INBOX":{"cur_idx":0}} + pList=host.memory.getProfilesList #shorter :) + self.__mailboxes=dict(zip(pList(),len(pList())*[{}])) + self.data=host.memory.getPrivate("MAILDIR_data") or dict(zip(pList(),len(pList())*[{"INBOX":{"cur_idx":0}}])) #Create empty box for each profile #a value in the dictionnary for a mailbox is a dictionnary with the following value # - cur_idx: value of the current unique integer increment (UID) # - message_id (as returned by MaildirMailbox): a tuple of (UID, [flag1, flag2, ...]) @@ -74,18 +76,26 @@ debug('Destroying MaildirBox') self.host.memory.setPrivate('MAILDIR_data',self.data) - def accessMessageBox(self, boxname, observer=None): + def accessMessageBox(self, boxname, observer=None, profile_key='@DEFAULT@'): """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) + profile = self.host.memory.getProfileName(profile_key) + if not profile: + raise ProfileUnknownError + if not self.__mailboxes[profile].has_key(boxname): + self.__mailboxes[profile][boxname]=MailboxUser(self, boxname, observer, profile=profile) else: if observer: - self.addObserver(observer, boxname) - return self.__mailboxes[boxname] + self.addObserver(observer, profile, boxname) + return self.__mailboxes[profile][boxname] - def _removeBoxAccess(self, boxname, mailboxUser): + def _getProfilePath(self, profile): + """Return a unique path for profile's mailbox + The path must be unique, usable as a dir name, and bijectional""" + return profile.replace('/','_').replace('..','_') #FIXME: this is too naive to work well, must be improved + + def _removeBoxAccess(self, boxname, mailboxUser, profile): """Remove a reference to a box @param name: name of the box @param mailboxUser: MailboxUser instance""" @@ -93,32 +103,33 @@ 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] + assert self.__mailboxes[profile][boxname]==mailboxUser + del __mailboxes[profile][boxname] - def _checkBoxReference(self, boxname): + def _checkBoxReference(self, boxname, profile): """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] + if self.__mailboxes.has_key(profile): + if self.__mailboxes[profile].has_key(boxname): + return self.__mailboxes[profile][boxname] - def __getBoxData(self, boxname): + def __getBoxData(self, boxname, profile): """Return the date of a box""" try: - return self.data[boxname] #the boxname MUST exist in the data + return self.data[profile][boxname] #the boxname MUST exist in the data except KeyError: err_msg=_("Boxname doesn't exist in internal data") error(_("INTERNAL ERROR: ") + err_msg) raise MaildirError(err_msg) - def getUid(self, boxname, message_id): + def getUid(self, boxname, message_id, profile): """Return an unique integer, always ascending, for a message This is mainly needed for the IMAP protocol @param boxname: name of the box where the message is @param message_id: unique id of the message as given by MaildirMailbox @return: Integer UID""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) if box_data.has_key(message_id): ret = box_data[message_id][0] else: @@ -128,40 +139,40 @@ self.host.memory.setPrivate('MAILDIR_data',self.data) return ret - def getNextUid(self, boxname): + def getNextUid(self, boxname, profile): """Return next unique integer that will generated This is mainly needed for the IMAP protocol @param boxname: name of the box where the message is @return: Integer UID""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) return box_data['cur_idx']+1 - def getNextExistingUid(self, boxname, uid): + def getNextExistingUid(self, boxname, uid, profile): """Give the next uid of existing message @param boxname: name of the box where the message is @param uid: uid to start from @return: uid or None if the is no more message""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) idx=uid+1 - while self.getIdFromUid(boxname, idx) == None: #TODO: this is highly inefficient because getIdfromUid is inefficient, fix this + while self.getIdFromUid(boxname, idx, profile) == None: #TODO: this is highly inefficient because getIdfromUid is inefficient, fix this idx+=1 if idx>box_data['cur_idx']: return None return idx - def getMaxUid(self, boxname): + def getMaxUid(self, boxname, profile): """Give the max existing uid @param boxname: name of the box where the message is @return: uid""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) return box_data['cur_idx'] - def getIdFromUid(self, boxname, message_uid): + def getIdFromUid(self, boxname, message_uid, profile): """Return the message unique id from it's integer UID @param boxname: name of the box where the message is @param message_uid: unique integer identifier @return: unique id of the message as given by MaildirMailbox or None if not found""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) for message_id in box_data.keys(): #TODO: this is highly inefficient on big mailbox, must be replaced in the future if message_id == 'cur_idx': continue @@ -169,36 +180,38 @@ return message_id return None - def getFlags(self, boxname, mess_id): + def getFlags(self, boxname, mess_id, profile): """Return the messages flags @param boxname: name of the box where the message is @param message_idx: message id as given by MaildirMailbox @return: list of strings""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) if not box_data.has_key(mess_id): raise MailboxException("Trying to get flags from an unexisting message") return box_data[mess_id][1] - def setFlags(self, boxname, mess_id, flags): + def setFlags(self, boxname, mess_id, flags, profile): """Change the flags of the message @param boxname: name of the box where the message is @param message_idx: message id as given by MaildirMailbox @param flags: list of strings """ - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) assert(type(flags)==list) + flags=[flag.upper() for flag in flags] #we store every flag UPPERCASE if not box_data.has_key(mess_id): raise MailboxException("Trying to set flags for an unexisting message") box_data[mess_id][1]=flags self.host.memory.setPrivate('MAILDIR_data',self.data) - def getMessageIdsWithFlag(self, boxname, flag): + def getMessageIdsWithFlag(self, boxname, flag, profile): """Return ids of messages where a flag is set @param boxname: name of the box where the message is @param flag: flag to check @return: list of id (as given by MaildirMailbox)""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) assert(isinstance(flag,basestring)) + flag=flag.upper() result = [] for key in box_data: if key=='cur_idx': @@ -207,20 +220,20 @@ result.append(key) return result - def purgeDeleted(self, boxname): + def purgeDeleted(self, boxname, profile): """Remove data for messages with flag "\\Deleted" @param boxname: name of the box where the message is """ - box_data = self.__getBoxData(boxname) - for mess_id in self.getMessageIdsWithFlag(boxname,"\\Deleted"): + box_data = self.__getBoxData(boxname, profile) + for mess_id in self.getMessageIdsWithFlag(boxname,"\\Deleted", profile): del(box_data[mess_id]) self.host.memory.setPrivate('MAILDIR_data',self.data) - def cleanTable(self, boxname, existant_id): + def cleanTable(self, boxname, existant_id, profile): """Remove mails which no longuer exist from the table @param boxname: name of the box to clean @param existant_id: list of id which actually exist""" - box_data = self.__getBoxData(boxname) + box_data = self.__getBoxData(boxname, profile) to_remove=[] for key in box_data: if key not in existant_id and key!="cur_idx": @@ -229,7 +242,7 @@ del box_data[key] - def MessageReceivedTrigger(self, message): + def MessageReceivedTrigger(self, message, profile): """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 @@ -239,44 +252,44 @@ type = message['type'] if message.hasAttribute('type') else 'chat' #FIXME: check specs if message['type'] != 'normal': return True - self.accessMessageBox("INBOX").addMessage(message) + self.accessMessageBox("INBOX", profile_key=profile).addMessage(message) return False - def addObserver(self, callback, boxname, signal="NEW_MESSAGE"): + def addObserver(self, callback, profile, 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) + if not self.__observed.has_key((profile,boxname)): + self.__observed[(profile,boxname)]={} + if not self.__observed[(profile,boxname)].has_key(signal): + self.__observed[(profile,boxname)][signal]=set() + self.__observed[(profile,boxname)][signal].add(callback) - def removeObserver(self, callback, boxname, signal="NEW_MESSAGE"): + def removeObserver(self, callback, profile, 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): + if not self.__observed.has_key((profile,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): + if not self.__observed[(profile,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]: + if not callback in self.__observed[(profile,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) + self.__observed[(profile,boxname)][signal].remove(callback) - def emitSignal(self, boxname, signal_name): + def emitSignal(self, profile, boxname, signal_name): """Emit the signal to observer""" - debug('emitSignal %s %s' %(boxname, signal_name)) + debug('emitSignal %s %s %s' %(profile, boxname, signal_name)) try: - for observer_cb in self.__observed[boxname][signal_name]: + for observer_cb in self.__observed[(profile, boxname)][signal_name]: observer_cb() except KeyError: pass @@ -304,27 +317,32 @@ mail['Subject'] = e.children[0].encode('utf-8') return mail.as_string() - def __init__(self, _maildir, name, observer=None): + def __init__(self, _maildir, name, observer=None, profile="@NONE@"): """@param _maildir: the main MaildirBox instance @param name: name of the mailbox + @param profile: real profile (ie not a profile_key) THIS OBJECT MUST NOT BE USED DIRECTLY: use MaildirBox.accessMessageBox instead""" - if _maildir._checkBoxReference(self): + if _maildir._checkBoxReference(name, profile): error ("INTERNAL ERROR: MailboxUser MUST NOT be instancied directly") raise MailboxException('double MailboxUser instanciation') if name!="INBOX": raise NotImplementedError self.name=name + self.profile=profile self.maildir=_maildir - mailbox_path = os.path.expanduser(os.path.join(self.maildir.host.get_const('local_dir'), MAILDIR_PATH)) + profile_path = self.maildir._getProfilePath(profile) + full_profile_path = os.path.join(os.path.expanduser(self.maildir.host.get_const('local_dir')),profile_path) + if not os.path.exists(full_profile_path): + os.makedirs(full_profile_path,0700) + mailbox_path = os.path.join(full_profile_path, MAILDIR_PATH) self.mailbox_path=mailbox_path self.mailbox = maildir.MaildirMailbox(mailbox_path) self.observer=observer self.__uid_table_update() - if observer: - debug("adding observer for %s" % name) - self.maildir.addObserver(observer, name, "NEW_MESSAGE") + debug("adding observer for %s (%s)" % (name,profile)) + self.maildir.addObserver(observer, profile, name, "NEW_MESSAGE") def __uid_table_update(self): existant_id=[] @@ -332,14 +350,14 @@ #we update the uid table existant_id.append(self.getId(mess_idx)) self.getUid(mess_idx) - self.maildir.cleanTable(self.name, existant_id) + self.maildir.cleanTable(self.name, existant_id, profile=self.profile) def __del__(self): if observer: debug("removing observer for %s" % self.name) self._maildir.removeObserver(observer, self.name, "NEW_MESSAGE") - self._maildir._removeBoxAccess(self.name, self) + self.maildir._removeBoxAccess(self.name, self, profile=self.profile) def addMessage(self, message): """Add a message to the box @@ -348,8 +366,9 @@ def emitSignal(self, ignore, signal): """Emit the signal to the observers""" - self.getUid(self.getMessageCount()-1) #we make an uid for the last message added - self.maildir.emitSignal(self.name, signal) + if signal=="NEW_MESSAGE": + self.getUid(self.getMessageCount()-1) #XXX: we make an uid for the last message added + self.maildir.emitSignal(self.profile, self.name, signal) def getId(self, mess_idx): """Return the Unique ID of the message @@ -359,16 +378,16 @@ def getUid(self, mess_idx): """Return a unique interger id for the message, always ascending""" mess_id=self.getId(mess_idx) - return self.maildir.getUid(self.name,mess_id) + return self.maildir.getUid(self.name,mess_id, profile=self.profile) def getNextUid(self): - return self.maildir.getNextUid(self.name) + return self.maildir.getNextUid(self.name, profile=self.profile) def getNextExistingUid(self, uid): - return self.maildir.getNextExistingUid(self.name, uid) + return self.maildir.getNextExistingUid(self.name, uid, profile=self.profile) def getMaxUid(self): - return self.maildir.getMaxUid(self.name) + return self.maildir.getMaxUid(self.name, profile=self.profile) def getMessageCount(self): """Return number of mails present in this box""" @@ -412,14 +431,14 @@ @param mess_idx: message index @return: list of strings""" id = self.getId(mess_idx) - return self.maildir.getFlags(self.name, id) + return self.maildir.getFlags(self.name, id, profile=self.profile) def getFlagsUid(self, mess_uid): """Return the flags of the message @param mess_uid: message unique identifier @return: list of strings""" - id = self.maildir.getIdFromUid(self.name,mess_uid) - return self.maildir.getFlags(self.name, id) + id = self.maildir.getIdFromUid(self.name,mess_uid, profile=self.profile) + return self.maildir.getFlags(self.name, id, profile=self.profile) def setFlags(self, mess_idx, flags): """Change the flags of the message @@ -427,21 +446,21 @@ @param flags: list of strings """ id = self.getId(mess_idx) - return self.maildir.setFlags(self.name, id, flags) + self.maildir.setFlags(self.name, id, flags, profile=self.profile) def setFlagsUid(self, mess_uid, flags): """Change the flags of the message @param mess_uid: message unique identifier @param flags: list of strings """ - id = self.maildir.getIdFromUid(self.name,mess_uid) - return self.maildir.setFlags(self.name, id, flags) + id = self.maildir.getIdFromUid(self.name,mess_uid, profile=self.profile) + return self.maildir.setFlags(self.name, id, flags, profile=self.profile) def getMessageIdsWithFlag(self, flag): """Return ids of messages where a flag is set @param flag: flag to check @return: list of id (as given by MaildirMailbox)""" - return self.maildir.getMessageIdsWithFlag(self.name,flag) + return self.maildir.getMessageIdsWithFlag(self.name,flag, profile=self.profile) def removeDeleted(self): """Actually delete message flagged "\\Deleted" @@ -451,7 +470,7 @@ print ("Deleting %s", mess_id) self.mailbox.deleteMessage(self.getIdxFromId(mess_id)) self.mailbox = maildir.MaildirMailbox(self.mailbox_path) #We need to reparse the dir to have coherent indexing - self.maildir.purgeDeleted(self.name) + self.maildir.purgeDeleted(self.name, profile=self.profile) def emptyTrash(self): """Delete everything in the .Trash dir"""