Extend handling of GMail labels header

Format headers X-Label and Keywords as a space separated list and all
other ones as comma-separated entities.  This makes OfflineIMAP label
handling to be compatible with some user agents that recognise these
headers.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Abdó Roig-Maranges 2012-11-28 18:29:23 +01:00 committed by Eygene Ryabinkin
parent 0e4afa9132
commit 789e047734
4 changed files with 96 additions and 26 deletions

View File

@ -277,7 +277,14 @@ remoterepository = RemoteExample
# #
#synclabels = no #synclabels = no
# Name of the header to use for label storage. # Name of the header to use for label storage. Format for the header
# value differs for different headers, because there are some de-facto
# standards set by popular clients:
# - X-Label or Keywords keep values separated with spaces; for these
# you, obviously, should not have label values that contain spaces;
# - X-Keywords use comma (',') as the separator.
# To be consistent with the usual To-like headers, for the rest of header
# types we use comma as the separator.
# #
#labelsheader = X-Keywords #labelsheader = X-Keywords

View File

@ -92,8 +92,8 @@ class GmailFolder(IMAPFolder):
else: else:
labels = set() labels = set()
labels = labels - self.ignorelabels labels = labels - self.ignorelabels
labels = ', '.join(sorted(labels)) labels_str = imaputil.format_labels_string(self.labelsheader, sorted(labels))
body = self.addmessageheader(body, self.labelsheader, labels) body = self.addmessageheader(body, self.labelsheader, labels_str)
if len(body)>200: if len(body)>200:
dbg_output = "%s...%s" % (str(body)[:150], str(body)[-50:]) dbg_output = "%s...%s" % (str(body)[:150], str(body)[-50:])
@ -183,11 +183,8 @@ class GmailFolder(IMAPFolder):
if not self.synclabels: if not self.synclabels:
return super(GmailFolder, self).savemessage(uid, content, flags, rtime) return super(GmailFolder, self).savemessage(uid, content, flags, rtime)
labels = self.getmessageheader(content, self.labelsheader) labels = imaputil.labels_from_header(self.labelsheader,
if labels: self.getmessageheader(content, self.labelsheader))
labels = set([lb.strip() for lb in labels.split(',') if len(lb.strip()) > 0])
else:
labels = set()
ret = super(GmailFolder, self).savemessage(uid, content, flags, rtime) ret = super(GmailFolder, self).savemessage(uid, content, flags, rtime)
self.savemessagelabels(ret, labels) self.savemessagelabels(ret, labels)

View File

@ -20,6 +20,7 @@ import os
from .Maildir import MaildirFolder from .Maildir import MaildirFolder
from offlineimap import OfflineImapError from offlineimap import OfflineImapError
import offlineimap.accounts import offlineimap.accounts
from offlineimap import imaputil
class GmailMaildirFolder(MaildirFolder): class GmailMaildirFolder(MaildirFolder):
"""Folder implementation to support adding labels to messages in a Maildir. """Folder implementation to support adding labels to messages in a Maildir.
@ -78,12 +79,10 @@ class GmailMaildirFolder(MaildirFolder):
content = file.read() content = file.read()
file.close() file.close()
labels = self.getmessageheader(content, self.labelsheader) self.messagelist[uid]['labels'] = \
if labels: imaputil.labels_from_header(self.labelsheader,
labels = set([lb.strip() for lb in labels.split(',') if len(lb.strip()) > 0]) self.getmessageheader(content, self.labelsheader))
else:
labels = set()
self.messagelist[uid]['labels'] = labels
return self.messagelist[uid]['labels'] return self.messagelist[uid]['labels']
@ -103,11 +102,8 @@ class GmailMaildirFolder(MaildirFolder):
if not self.synclabels: if not self.synclabels:
return super(GmailMaildirFolder, self).savemessage(uid, content, flags, rtime) return super(GmailMaildirFolder, self).savemessage(uid, content, flags, rtime)
labels = self.getmessageheader(content, self.labelsheader) labels = imaputil.labels_from_header(self.labelsheader,
if labels: self.getmessageheader(content, self.labelsheader))
labels = set([lb.strip() for lb in labels.split(',') if len(lb.strip()) > 0])
else:
labels = set()
ret = super(GmailMaildirFolder, self).savemessage(uid, content, flags, rtime) ret = super(GmailMaildirFolder, self).savemessage(uid, content, flags, rtime)
# Update the mtime and labels # Update the mtime and labels
@ -130,12 +126,9 @@ class GmailMaildirFolder(MaildirFolder):
content = file.read() content = file.read()
file.close() file.close()
oldlabels = self.getmessageheader(content, self.labelsheader) oldlabels = imaputil.labels_from_header(self.labelsheader,
self.getmessageheader(content, self.labelsheader))
if oldlabels:
oldlabels = set([lb.strip() for lb in oldlabels.split(',') if len(lb.strip()) > 0])
else:
oldlabels = set()
labels = labels - ignorelabels labels = labels - ignorelabels
ignoredlabels = oldlabels & ignorelabels ignoredlabels = oldlabels & ignorelabels
@ -146,13 +139,14 @@ class GmailMaildirFolder(MaildirFolder):
return return
# Change labels into content # Change labels into content
labels_str = ', '.join(sorted(labels | ignoredlabels)) labels_str = imaputil.format_labels_string(self.labelsheader,
sorted(labels | ignoredlabels))
content = self.addmessageheader(content, self.labelsheader, labels_str) content = self.addmessageheader(content, self.labelsheader, labels_str)
rtime = self.messagelist[uid].get('rtime', None) rtime = self.messagelist[uid].get('rtime', None)
# write file with new labels to a unique file in tmp # write file with new labels to a unique file in tmp
messagename = self.new_message_filename(uid, set()) messagename = self.new_message_filename(uid, set())
tmpname = self.save_tmp_file(messagename, content) tmpname = self.save_to_tmp_file(messagename, content)
tmppath = os.path.join(self.getfullname(), tmpname) tmppath = os.path.join(self.getfullname(), tmpname)
# move to actual location # move to actual location

View File

@ -21,6 +21,12 @@ import string
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
## Globals
# Message headers that use space as the separator (for label storage)
SPACE_SEPARATED_LABEL_HEADERS = ('X-Label', 'Keywords')
def __debug(*args): def __debug(*args):
msg = [] msg = []
for arg in args: for arg in args:
@ -260,3 +266,69 @@ def __split_quoted(string):
rest = rest[next_q + 1:] rest = rest[next_q + 1:]
if not is_escaped: if not is_escaped:
return (quoted, rest.lstrip()) return (quoted, rest.lstrip())
def format_labels_string(header, labels):
"""
Formats labels for embedding into a message,
with format according to header name.
Headers from SPACE_SEPARATED_LABEL_HEADERS keep space-separated list
of labels, the rest uses comma (',') as the separator.
Also see parse_labels_string() and modify it accordingly
if logics here gets changed.
"""
if header in SPACE_SEPARATED_LABEL_HEADERS:
sep = ' '
else:
sep = ','
return sep.join(labels)
def parse_labels_string(header, labels_str):
"""
Parses a string into a set of labels, with a format according to
the name of the header.
See __format_labels_string() for explanation on header handling
and keep these two functions synced with each other.
TODO: add test to ensure that
format_labels_string * parse_labels_string is unity
and
parse_labels_string * format_labels_string is unity
"""
if header in SPACE_SEPARATED_LABEL_HEADERS:
sep = ' '
else:
sep = ','
labels = labels_str.strip().split(sep)
return set([l.strip() for l in labels if l.strip()])
def labels_from_header(header_name, header_value):
"""
Helper that builds label set from the corresponding header value.
Arguments:
- header_name: name of the header that keeps labels;
- header_value: value of the said header, can be None
Returns: set of labels parsed from the header (or empty set).
"""
if header_value:
labels = parse_labels_string(header_name, header_value)
else:
labels = set()
return labels