changeset 255:55b750017b71

plugin IMAP, plugin Maildir: added flag, IMAP's uid management
author Goffi <goffi@goffi.org>
date Mon, 17 Jan 2011 21:26:16 +0100
parents 9fc32d1d9046
children f5181f6dd98f
files src/plugins/plugin_misc_imap.py src/plugins/plugin_misc_maildir.py
diffstat 2 files changed, 175 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/plugin_misc_imap.py	Mon Jan 17 04:23:31 2011 +0100
+++ b/src/plugins/plugin_misc_imap.py	Mon Jan 17 21:26:16 2011 +0100
@@ -75,9 +75,10 @@
 class Message():
     implements(imap4.IMessage)
 
-    def __init__(self, uid, mess_fp):
+    def __init__(self, uid, flags, mess_fp):
         debug('Message Init')
         self.uid=uid
+        self.flags=flags
         self.mess_fp=mess_fp
         self.message=Parser().parse(mess_fp)
     
@@ -93,7 +94,7 @@
         @return: The flags, represented as strings.
         """
         debug('getFlags')
-        return []
+        return self.flags
 
     def getInternalDate(self):
         """Retrieve the date internally associated with this message.
@@ -184,8 +185,9 @@
         @param message: The message sequence number
         @return: The UID of the message.
         """
-        debug ('getUID')
-        return self.mailbox.getUid(message)
+        debug ('getUID (%i)' % message)
+        #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number
+        return message
 
     def getMessageCount(self):
         """Return the number of messages in this mailbox.
@@ -199,13 +201,13 @@
         """Return the number of messages with the 'Recent' flag.
         """
         debug('getRecentCount')
-        return 0
+        return len(self.mailbox.getMessageIdsWithFlag('\\Recent'))
 
     def getUnseenCount(self):
         """Return the number of messages with the 'Unseen' flag.
         """
         debug('getUnseenCount')
-        return 1
+        return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\Seen'))
 
     def isWriteable(self):
         """Get the read/write status of the mailbox.
@@ -277,7 +279,7 @@
         or a Deferred whose callback will be invoked with such a list.
         """
         debug('expunge')
-        raise NotImplementedError
+        self.mailbox.removeDeleted()
 
     def fetch(self, messages, uid):
         """Retrieve one or more messages.
@@ -294,8 +296,7 @@
                     debug ('stopping iteration')
                     raise StopIteration
                 try:
-                    debug ('yielding (%s,%s)' % (mess_uid,Message(mess_uid,self.mailbox.getMessageUid(mess_uid))))
-                    yield (mess_uid,Message(mess_uid,self.mailbox.getMessageUid(mess_uid)))
+                    yield (mess_uid,Message(mess_uid,self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid)))
                 except IndexError:
                     continue
         else:
@@ -303,7 +304,7 @@
             for mess_idx in messages:
                 if mess_idx>self.getMessageCount():
                     raise StopIteration
-                yield (mess_idx,Message(mess_idx,self.mailbox.getMessage(mess_idx-1)))
+                yield (mess_idx,Message(mess_idx,self.mailbox.getFlags(mess_idx),self.mailbox.getMessage(mess_idx-1)))
 
     def store(self, messages, flags, mode, uid):
         """Set the flags of one or more messages.
@@ -321,7 +322,29 @@
         such a dict.
         """
         debug('store')
-        raise NotImplementedError
+
+        def updateFlags(getF,setF):
+            ret = {}
+            for mess_id in messages:
+                if (uid and mess_id == None) or (not uid and mess_id>self.getMessageCount()):
+                    break
+                _flags=set(getF(mess_id) if mode else [])
+                if mode==-1:
+                    _flags.difference_update(set(flags))
+                else:
+                    _flags.update(set(flags))
+                new_flags=list(_flags)
+                setF(mess_id, new_flags)
+                ret[mess_id] = new_flags
+            return ret
+            
+        if uid:
+            messages.last = self.mailbox.getMaxUid()
+            messages.getnext = self.mailbox.getNextExistingUid
+            return updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid)
+        else:
+            messages.last = self.getMessageCount()
+            return updateFlags(self.mailbox.getFlags,self.mailbox.setFlags)
 
     def getFlags(self):
         """Return the flags defined in this mailbox
@@ -329,8 +352,7 @@
         @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 []
+        return ['\Seen','\Answered','\Flagged','\Deleted','\Draft'] #TODO: add '\Recent'
 
     def getHierarchicalDelimiter(self):
         """Get the character which delimits namespaces for in this mailbox.
--- a/src/plugins/plugin_misc_maildir.py	Mon Jan 17 04:23:31 2011 +0100
+++ b/src/plugins/plugin_misc_maildir.py	Mon Jan 17 21:26:16 2011 +0100
@@ -70,7 +70,7 @@
         #the trigger
         host.trigger.add("MessageReceived", self.MessageReceivedTrigger)
 
-    def __destroy__(self):
+    def __del__(self):
         debug('Destroying MaildirBox')
         self.host.memory.setPrivate('MAILDIR_data',self.data)
 
@@ -103,39 +103,37 @@
         if self.__mailboxes.has_key(boxname):
             return self.__mailboxes[boxname]
 
+    def __getBoxData(self, boxname):
+        """Return the date of a box"""
+        try:
+            return self.data[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):
         """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"""
-        try:
-            box_data = self.data[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)
+        box_data = self.__getBoxData(boxname)
         if box_data.has_key(message_id):
             ret = box_data[message_id][0]
         else:
             box_data['cur_idx']+=1
-            box_data[message_id]=(box_data['cur_idx'],[])
+            box_data[message_id]=[box_data['cur_idx'],[]]
             ret = box_data[message_id]
         self.host.memory.setPrivate('MAILDIR_data',self.data)
         return ret
 
-
     def getNextUid(self, boxname):
         """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"""
-        try:
-            box_data = self.data[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)
+        box_data = self.__getBoxData(boxname)
         return box_data['cur_idx']+1
 
     def getNextExistingUid(self, boxname, uid):
@@ -143,14 +141,9 @@
         @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"""
-        try:
-            box_data = self.data[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)
+        box_data = self.__getBoxData(boxname)
         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) == None: #TODO: this is highly inefficient because getIdfromUid is inefficient, fix this
             idx+=1
             if idx>box_data['cur_idx']:
                 return None
@@ -160,27 +153,15 @@
         """Give the max existing uid
         @param boxname: name of the box where the message is
         @return: uid"""
-        try:
-            box_data = self.data[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)
+        box_data = self.__getBoxData(boxname)
         return box_data['cur_idx']
     
-    
-    def getIdfromUid(self, boxname, message_uid):
+    def getIdFromUid(self, boxname, message_uid):
         """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"""
-        try:
-            box_data = self.data[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)
-
+        box_data = self.__getBoxData(boxname)
         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
@@ -188,16 +169,58 @@
                 return message_id
         return None
 
+    def getFlags(self, boxname, mess_id):
+        """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)
+        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):
+        """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)
+        assert(type(flags)==list)
+        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):
+        """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)
+        assert(isinstance(flag,basestring))
+        result = []
+        for key in box_data:
+            if key=='cur_idx':
+                continue
+            if flag in box_data[key][1]:
+                result.append(key)
+        return result
+
+    def purgeDeleted(self, boxname):
+        """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"):
+           del(box_data[mess_id]) 
+        self.host.memory.setPrivate('MAILDIR_data',self.data)
+    
     def cleanTable(self, boxname, existant_id):
         """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"""
-        try:
-            box_data = self.data[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)
+        box_data = self.__getBoxData(boxname)
         to_remove=[]
         for key in box_data:
             if key not in existant_id and key!="cur_idx":
@@ -312,7 +335,7 @@
         self.maildir.cleanTable(self.name, existant_id)
     
 
-    def __destroy__(self):
+    def __del__(self):
         if observer:
             debug("removing observer for %s" % self.name)
             self._maildir.removeObserver(observer, self.name, "NEW_MESSAGE")
@@ -355,16 +378,83 @@
         """Return the full message
         @mess_idx: message index"""
         return self.mailbox.getMessage(mess_idx)
+
+    def getIdxFromUid(self, mess_uid):
+        """Return the message index from the uid
+        @param mess_uid: message unique identifier
+        @return: message index, as managed by MaildirMailbox"""
+        for mess_idx in range (self.getMessageCount()):
+            if self.getUid(mess_idx) == mess_uid:
+                return mess_idx
+        raise IndexError
+    
+    def getIdxFromId(self, mess_id):
+        """Return the message index from the unique index
+        @param mess_id: message unique index as given by MaildirMailbox
+        @return: message sequence index"""
+        for mess_idx in range (self.getMessageCount()):
+            if self.mailbox.getUidl(mess_idx) == mess_id:
+                return mess_idx
+        raise IndexError
     
     def getMessage(self, mess_idx):
         """Return the full message
-        @mess_idx: message index"""
+        @param mess_idx: message index"""
         return self.mailbox.getMessage(mess_idx)
 
     def getMessageUid(self, mess_uid):
         """Return the full message
-        @mess_idx: message unique identifier"""
-        for mess_idx in range (self.getMessageCount()):
-            if self.getUid(mess_idx) == mess_uid:
-                return self.mailbox.getMessage(mess_idx)
-        raise IndexError
+        @param mess_idx: message unique identifier"""
+        return self.mailbox.getMessage(self.getIdxFromUid(mess_uid))
+
+    def getFlags(self, mess_idx):
+        """Return the flags of the message
+        @param mess_idx: message index
+        @return: list of strings"""
+        id = self.getId(mess_idx)
+        return self.maildir.getFlags(self.name, id)
+    
+    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)
+
+    def setFlags(self, mess_idx, flags):
+        """Change the flags of the message
+        @param mess_idx: message index
+        @param flags: list of strings
+        """
+        id = self.getId(mess_idx)
+        return self.maildir.setFlags(self.name, id, flags)
+
+    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)
+
+    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)
+
+    def removeDeleted(self):
+        """Actually delete message flagged "\\Deleted"
+        Also purge the internal data of these messages
+        """
+        for mess_id in self.getMessageIdsWithFlag("\\Deleted"):
+            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)
+
+    def emptyTrash(self):
+        """Delete everything in the .Trash dir"""
+        import shutils
+        pdb.set_trace()
+