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:
parent
0e4afa9132
commit
789e047734
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user