Merge branch 'ia/maildir-keywords' into next
This commit is contained in:
commit
5e0f733c3e
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user