From f04862b28f9c14ba3a21f65b81240411e99cdb98 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 16 Nov 2011 22:34:44 +0100 Subject: [PATCH] wip: Webmail big refactoring and performance improvment Better IMAP commands. --- extensions/webmail/imaputils.py | 106 ++++++++++++------- extensions/webmail/templatetags/webextras.py | 2 +- static/js/anchor_nav.js | 4 + 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/extensions/webmail/imaputils.py b/extensions/webmail/imaputils.py index 98c7a56e8..49dc4f093 100644 --- a/extensions/webmail/imaputils.py +++ b/extensions/webmail/imaputils.py @@ -6,12 +6,14 @@ import imaplib, ssl, email import re from functools import wraps -from imapclient.response_parser import * +from imapclient.response_parser import parse_fetch_response from modoboa.lib import parameters from modoboa.lib.connections import * from modoboa.lib.webutils import static_url from exceptions import ImapError +imaplib.Debug = 4 + class capability(object): """ Simple decorator to check if the server presents the required @@ -66,12 +68,14 @@ class IMAPconnector(object): """ if name in ['FETCH', 'SORT', 'STORE']: try: - typ, data = self.m.uid(name, *args, **kwargs) + typ, data = self.m.uid(name, *args) except imaplib.IMAP4.error, e: raise ImapError(e) if typ == "NO": raise ImapError(data) - return parse_fetch_response(data) + if name == 'FETCH': + return parse_fetch_response(data) + return data try: typ, data = self.m._simple_command(name, *args) @@ -140,20 +144,39 @@ class IMAPconnector(object): else: criterion = "REVERSE DATE" folder = kwargs.has_key("folder") and kwargs["folder"] or None - (status, data) = self.m.select(self._encodefolder(folder)) - (status, data) = self.m.sort("(%s)" % criterion, "UTF-8", "(NOT DELETED)", - *self.criterions) + self.select_mailbox(folder, False) + data = self._cmd("SORT", "(%s)" % criterion, "UTF-8", "(NOT DELETED)", + *self.criterions) self.messages = data[0].split() self.getquota(folder) return len(self.messages) - def unseen_messages(self, folder): - """Return the number of unseen messages for folder""" - self.m.select(self._encodefolder(folder), True) - status, data = self.m.search("UTF-8", "(NOT DELETED UNSEEN)") - if status != "OK": - return - return len(data[0].split()) + def select_mailbox(self, name, readonly=True): + """Issue a SELECT/EXAMINE command to the server + + The given name is first 'imap-utf7' encoded. + + :param name: mailbox's name + :param readonly: + """ + name = name.encode("imap4-utf-7") + if readonly: + self._cmd("EXAMINE", name) + else: + self._cmd("SELECT", name) + + def unseen_messages(self, mailbox): + """Return the number of unseen messages + + :param mailbox: the mailbox's name + :return: an integer + """ + # self.select_mailbox(mailbox) + # data = self._cmd("SEARCH", "UTF-8", "(NOT DELETED UNSEEN)") + # return len(data[0].split()) + data = self._cmd("STATUS", mailbox.encode("imap4-utf-7"), "(UNSEEN)") + print data + return 0 def _encodefolder(self, folder): if not folder: @@ -205,13 +228,27 @@ class IMAPconnector(object): @capability('LIST-EXTENDED', '_listfolders_simple') def _listfolders(self, topmailbox='', md_mailboxes=[]): resp = self._cmd("LSUB", topmailbox, "%", "RETURN", "(CHILDREN)") - print resp + result = [] for mb in resp: flags, delimiter, name, childinfo = \ self.listextended_response_pattern.match(mb).groups() flags = flags.split(' ') - print flags - return [] + name = name.decode("imap4-utf-7") + present = False + for mdm in md_mailboxes: + if mdm["name"] == name: + present = True + break + if present: + continue + descr = dict(name=name) + if r'\HasChildren' in flags: + descr["path"] = name + descr["sub"] = [] + descr["class"] = "subfolders" + result += [descr] + from operator import itemgetter + return sorted(result, key=itemgetter("name")) def getfolders(self, user, unseen_messages=True): md_mailboxes = [{"name" : "INBOX", "class" : "inbox"}, @@ -314,11 +351,8 @@ class IMAPconnector(object): msg = email.message_from_string(data[0][1] + data[1][1]) return msg - def fetch(self, start=None, stop=None, folder=None, all=False, **kwargs): - if not start and not stop: - return [] - result = [] - self.m.select(self._encodefolder(folder), True) + def fetch(self, start, stop=None, folder=None, all=False, **kwargs): + self.select_mailbox(folder, False) if start and stop: submessages = self.messages[start - 1:stop] range = ",".join(submessages) @@ -326,27 +360,19 @@ class IMAPconnector(object): submessages = [start] range = start if not all: - query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])' + query = '(FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])' else: query = '(RFC822)' - typ, data = self.m.fetch(range, query) - if not folder: - folder = "INBOX" - tmpdict = {} - - for response_part in data: - if isinstance(response_part, tuple): - imapid = response_part[0].split()[0] - flags = imaplib.ParseFlags(response_part[0]) - msg = email.message_from_string(response_part[1]) - msg["imapid"] = imapid - if not "\\Seen" in flags: - msg["class"] = "unseen" - if "\\Answered" in flags: - msg["img_flags"] = static_url("pics/answered.png") - tmpdict[imapid] = msg - for id in submessages: - result += [tmpdict[id]] + data = self._cmd("FETCH", range, query) + result = [] + for uid in submessages: + msg = email.message_from_string(data[int(uid)]['BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)]']) + msg['imapid'] = uid + if not r'\Seen' in data[int(uid)]['FLAGS']: + msg['class'] = 'unseen' + if r'\Answered' in data[int(uid)]['FLAGS']: + msg['img_flags'] = static_url('pics/answered.png') + result += [msg] return result def get_imapconnector(request): diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py index 13d3a4c0e..74b9bdbd4 100644 --- a/extensions/webmail/templatetags/webextras.py +++ b/extensions/webmail/templatetags/webextras.py @@ -127,7 +127,7 @@ def print_folders(folders, selected=None, withunseen=False, withmenu=False): if selected == name: cssclass += " selected" result += "
  • \n" % (name, cssclass) - if fd.has_key("sub") and len(fd["sub"]): + if fd.has_key("sub"): if selected is not None and selected != name and selected.count(name): ul_state = "visible" div_state = "expanded" diff --git a/static/js/anchor_nav.js b/static/js/anchor_nav.js index 7d7ae4a6f..732f09b4a 100644 --- a/static/js/anchor_nav.js +++ b/static/js/anchor_nav.js @@ -207,6 +207,10 @@ var AnchorNavigation = new Class({ url: query, noCache : true, onSuccess: function(resp) { + if (resp.status == "ko") { + infobox.error(resp.respmsg); + return; + } var callback = ($defined(resp.callback)) ? resp.callback : "default"; this.callbacks[callback](resp); infobox.info(gettext("Done"));