Merge branch 'ia/maildir-keywords' into next
This commit is contained in:
		@@ -536,6 +536,31 @@ localfolders = ~/Test
 | 
			
		||||
#
 | 
			
		||||
#filename_use_mail_timestamp = no
 | 
			
		||||
 | 
			
		||||
# This option stands in the [Repository LocalExample] section.
 | 
			
		||||
#
 | 
			
		||||
# Map IMAP [user-defined] keywords to lowercase letters, similar to Dovecot's
 | 
			
		||||
# format described in http://wiki2.dovecot.org/MailboxFormat/Maildir . This
 | 
			
		||||
# option makes sense for the Maildir type, only.
 | 
			
		||||
#
 | 
			
		||||
# Configuration example:
 | 
			
		||||
#      customflag_x = some_keyword
 | 
			
		||||
#
 | 
			
		||||
# With the configuration example above enabled, all IMAP messages that have
 | 
			
		||||
# 'some_keyword' in their FLAGS field will have an 'x' in the flags part of the
 | 
			
		||||
# maildir filename:
 | 
			
		||||
#      1234567890.M20046P2137.mailserver,S=4542,W=4642:2,Sx
 | 
			
		||||
#
 | 
			
		||||
# Valid fields are customflag_[a-z], valid values are whatever the IMAP server
 | 
			
		||||
# allows.
 | 
			
		||||
#
 | 
			
		||||
# Comparison in offlineimap is case-sensitive.
 | 
			
		||||
#
 | 
			
		||||
# This option is EXPERIMENTAL.
 | 
			
		||||
#
 | 
			
		||||
#customflag_a = some_keyword
 | 
			
		||||
#customflag_b = $OtherKeyword
 | 
			
		||||
#customflag_c = NonJunk
 | 
			
		||||
#customflag_d = ToDo
 | 
			
		||||
 | 
			
		||||
[Repository GmailLocalExample]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -420,6 +420,11 @@ class BaseFolder(object):
 | 
			
		||||
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def getmessagekeywords(self, uid):
 | 
			
		||||
        """Returns the keywords for the specified message."""
 | 
			
		||||
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def savemessageflags(self, uid, flags):
 | 
			
		||||
        """Sets the specified message's flags to the given set.
 | 
			
		||||
 | 
			
		||||
@@ -903,6 +908,45 @@ class BaseFolder(object):
 | 
			
		||||
                    return #don't delete messages in dry-run mode
 | 
			
		||||
                dstfolder.deletemessages(deletelist)
 | 
			
		||||
 | 
			
		||||
    def combine_flags_and_keywords(self, uid, dstfolder):
 | 
			
		||||
        """Combine the message's flags and keywords using the mapping for the
 | 
			
		||||
        destination folder."""
 | 
			
		||||
 | 
			
		||||
        # Take a copy of the message flag set, otherwise
 | 
			
		||||
        # __syncmessagesto_flags() will fail because statusflags is actually a
 | 
			
		||||
        # reference to selfflags (which it should not, but I don't have time to
 | 
			
		||||
        # debug THAT).
 | 
			
		||||
        selfflags = set(self.getmessageflags(uid))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            keywordmap = dstfolder.getrepository().getkeywordmap()
 | 
			
		||||
            if keywordmap is None:
 | 
			
		||||
                return selfflags
 | 
			
		||||
 | 
			
		||||
            knownkeywords = set(keywordmap.keys())
 | 
			
		||||
 | 
			
		||||
            selfkeywords = self.getmessagekeywords(uid)
 | 
			
		||||
 | 
			
		||||
            if not knownkeywords >= selfkeywords:
 | 
			
		||||
                #some of the message's keywords are not in the mapping, so
 | 
			
		||||
                #skip them
 | 
			
		||||
 | 
			
		||||
                skipped_keywords = list(selfkeywords - knownkeywords)
 | 
			
		||||
                selfkeywords &= knownkeywords
 | 
			
		||||
 | 
			
		||||
                self.ui.warn("Unknown keywords skipped: %s\n"
 | 
			
		||||
                    "You may want to change your configuration to include "
 | 
			
		||||
                    "those\n" % (skipped_keywords))
 | 
			
		||||
 | 
			
		||||
            keywordletterset = set([keywordmap[keyw] for keyw in selfkeywords])
 | 
			
		||||
 | 
			
		||||
            #add the mapped keywords to the list of message flags
 | 
			
		||||
            selfflags |= keywordletterset
 | 
			
		||||
        except NotImplementedError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        return selfflags
 | 
			
		||||
 | 
			
		||||
    def __syncmessagesto_flags(self, dstfolder, statusfolder):
 | 
			
		||||
        """Pass 3: Flag synchronization.
 | 
			
		||||
 | 
			
		||||
@@ -925,13 +969,13 @@ class BaseFolder(object):
 | 
			
		||||
            if uid < 0 or not dstfolder.uidexists(uid):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            selfflags = self.getmessageflags(uid)
 | 
			
		||||
 | 
			
		||||
            if statusfolder.uidexists(uid):
 | 
			
		||||
                statusflags = statusfolder.getmessageflags(uid)
 | 
			
		||||
            else:
 | 
			
		||||
                statusflags = set()
 | 
			
		||||
 | 
			
		||||
            selfflags = self.combine_flags_and_keywords(uid, dstfolder)
 | 
			
		||||
 | 
			
		||||
            addflags = selfflags - statusflags
 | 
			
		||||
            delflags = statusflags - selfflags
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -251,8 +251,10 @@ class IMAPFolder(BaseFolder):
 | 
			
		||||
                uid = long(options['UID'])
 | 
			
		||||
                self.messagelist[uid] = self.msglist_item_initializer(uid)
 | 
			
		||||
                flags = imaputil.flagsimap2maildir(options['FLAGS'])
 | 
			
		||||
                keywords = imaputil.flagsimap2keywords(options['FLAGS'])
 | 
			
		||||
                rtime = imaplibutil.Internaldate2epoch(messagestr)
 | 
			
		||||
                self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
 | 
			
		||||
                self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime,
 | 
			
		||||
                    'keywords': keywords}
 | 
			
		||||
        self.ui.messagelistloaded(self.repository, self, self.getmessagecount())
 | 
			
		||||
 | 
			
		||||
    def dropmessagelistcache(self):
 | 
			
		||||
@@ -309,6 +311,10 @@ class IMAPFolder(BaseFolder):
 | 
			
		||||
    def getmessageflags(self, uid):
 | 
			
		||||
        return self.messagelist[uid]['flags']
 | 
			
		||||
 | 
			
		||||
    # Interface from BaseFolder
 | 
			
		||||
    def getmessagekeywords(self, uid):
 | 
			
		||||
        return self.messagelist[uid]['keywords']
 | 
			
		||||
 | 
			
		||||
    def __generate_randomheader(self, content):
 | 
			
		||||
        """Returns a unique X-OfflineIMAP header
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -135,9 +135,7 @@ class MaildirFolder(BaseFolder):
 | 
			
		||||
                uid = long(uidmatch.group(1))
 | 
			
		||||
        flagmatch = self.re_flagmatch.search(filename)
 | 
			
		||||
        if flagmatch:
 | 
			
		||||
            # Filter out all lowercase (custom maildir) flags. We don't
 | 
			
		||||
            # handle them yet.
 | 
			
		||||
            flags = set((c for c in flagmatch.group(1) if not c.islower()))
 | 
			
		||||
            flags = set((c for c in flagmatch.group(1)))
 | 
			
		||||
        return prefix, uid, fmd5, flags
 | 
			
		||||
 | 
			
		||||
    def _scanfolder(self, min_date=None, min_uid=None):
 | 
			
		||||
@@ -149,7 +147,7 @@ class MaildirFolder(BaseFolder):
 | 
			
		||||
        with similar UID's (e.g. the UID was reassigned much later).
 | 
			
		||||
 | 
			
		||||
        Maildir flags are: R (replied) S (seen) T (trashed) D (draft) F
 | 
			
		||||
        (flagged).
 | 
			
		||||
        (flagged), plus lower-case letters for custom flags.
 | 
			
		||||
        :returns: dict that can be used as self.messagelist.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@@ -414,8 +412,7 @@ class MaildirFolder(BaseFolder):
 | 
			
		||||
 | 
			
		||||
        if flags != self.messagelist[uid]['flags']:
 | 
			
		||||
            # Flags have actually changed, construct new filename Strip
 | 
			
		||||
            # off existing infostring (possibly discarding small letter
 | 
			
		||||
            # flags that dovecot uses TODO)
 | 
			
		||||
            # off existing infostring
 | 
			
		||||
            infomatch = self.re_flagmatch.search(filename)
 | 
			
		||||
            if infomatch:
 | 
			
		||||
                filename = filename[:-len(infomatch.group())] #strip off
 | 
			
		||||
 
 | 
			
		||||
@@ -195,6 +195,14 @@ def flagsimap2maildir(flagstring):
 | 
			
		||||
            retval.add(maildirflag)
 | 
			
		||||
    return retval
 | 
			
		||||
 | 
			
		||||
def flagsimap2keywords(flagstring):
 | 
			
		||||
    """Convert string '(\\Draft \\Deleted somekeyword otherkeyword)' into a
 | 
			
		||||
    keyword set (somekeyword otherkeyword)."""
 | 
			
		||||
 | 
			
		||||
    imapflagset = set(flagstring[1:-1].split())
 | 
			
		||||
    serverflagset = set([flag for (flag, c) in flagmap])
 | 
			
		||||
    return imapflagset - serverflagset
 | 
			
		||||
 | 
			
		||||
def flagsmaildir2imap(maildirflaglist):
 | 
			
		||||
    """Convert set of flags ([DR]) into a string '(\\Deleted \\Draft)'."""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -133,6 +133,9 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
 | 
			
		||||
    def getsep(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def getkeywordmap(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def should_sync_folder(self, fname):
 | 
			
		||||
        """Should this folder be synced?"""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,14 @@ class MaildirRepository(BaseRepository):
 | 
			
		||||
        if not os.path.isdir(self.root):
 | 
			
		||||
            os.mkdir(self.root, 0o700)
 | 
			
		||||
 | 
			
		||||
        # Create the keyword->char mapping
 | 
			
		||||
        self.keyword2char = dict()
 | 
			
		||||
        for c in 'abcdefghijklmnopqrstuvwxyz':
 | 
			
		||||
            confkey = 'customflag_' + c
 | 
			
		||||
            keyword = self.getconf(confkey, None)
 | 
			
		||||
            if keyword is not None:
 | 
			
		||||
                self.keyword2char[keyword] = c
 | 
			
		||||
 | 
			
		||||
    def _append_folder_atimes(self, foldername):
 | 
			
		||||
        """Store the atimes of a folder's new|cur in self.folder_atimes"""
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +80,9 @@ class MaildirRepository(BaseRepository):
 | 
			
		||||
    def getsep(self):
 | 
			
		||||
        return self.getconf('sep', '.').strip()
 | 
			
		||||
 | 
			
		||||
    def getkeywordmap(self):
 | 
			
		||||
        return self.keyword2char if len(self.keyword2char) > 0 else None
 | 
			
		||||
 | 
			
		||||
    def makefolder(self, foldername):
 | 
			
		||||
        """Create new Maildir folder if necessary
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user