From c7cfac8e0a0ce6083a73589e84d64d79d7f6da0a Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 1 Dec 2011 17:36:21 +0100 Subject: [PATCH] wip: webmail big refactoring and perfomance improvment Misc. changes. --- extensions/webmail/imaputils.py | 88 ++++++++++++- extensions/webmail/static/css/webmail.css | 8 ++ extensions/webmail/static/js/webmail.js | 63 ++++++--- .../webmail/templates/webmail/index.html | 1 + extensions/webmail/templatetags/webextras.py | 10 +- extensions/webmail/urls.py | 6 +- extensions/webmail/views.py | 120 ++++++++++-------- lib/email_listing.py | 2 +- static/css/default.css | 2 +- static/css/spinner.gif | Bin 1569 -> 2545 bytes static/js/anchor_nav.js | 39 +++++- templates/common/email_listing.html | 4 +- templates/nlayout.html | 1 + 13 files changed, 251 insertions(+), 93 deletions(-) diff --git a/extensions/webmail/imaputils.py b/extensions/webmail/imaputils.py index 57bce66aa..1750ac7f6 100644 --- a/extensions/webmail/imaputils.py +++ b/extensions/webmail/imaputils.py @@ -12,7 +12,7 @@ from modoboa.lib.connections import * from modoboa.lib.webutils import static_url from exceptions import ImapError -imaplib.Debug = 4 +#imaplib.Debug = 4 class capability(object): """ @@ -88,6 +88,32 @@ class IMAPconnector(object): return None return self.m.untagged_responses.pop(name) + def __find_content_in_bodystruct(self, bodystruct, mtype, stype, prefix=""): + """Retrieve the number (index) of a specific part + + This part number will generally be used inside a FETCH request + to specify a ``BODY.PEEK`` section. + + :param bodystruct: a BODYSTRUCTURE list + :param mtype: the MIME main type (like text) + :param stype: the MIME sub type (like plain) + :param prefix: the prefix that will be added to the current part number + :return: a tuple (index (None on error), encoding (string), size (int)) + """ + if type(bodystruct[0]) in [list, tuple]: + cpt = 1 + for part in bodystruct[0]: + nprefix = "%s" % cpt if prefix == "" else "%s.%d" % (prefix, cpt) + index, encoding, size = \ + self.__find_content_in_bodystruct(bodystruct[0], mtype, stype, nprefix) + if index: + return (index, encoding, size) + cpt += 1 + + if bodystruct[0] == mtype and bodystruct[1] == stype: + return ("1" if not len(prefix) else prefix, bodystruct[5], int(bodystruct[6])) + return (None, None, 0) + def refresh(self, user, password): """Check if current connection needs a refresh @@ -384,18 +410,24 @@ class IMAPconnector(object): msg = email.message_from_string(data[0][1] + data[1][1]) return msg - def fetch(self, start, stop=None, folder=None, all=False, **kwargs): - self.select_mailbox(folder, False) + def fetch(self, start, stop=None, mbox=None, **kwargs): + """Retrieve information about messages from the server + + Issue a FETCH command to retrieve information about one or + more messages (such as headers) from the server. + + :param start: index of the first message + :param stop: index of the last message (optionnal) + :param mbox: the mailbox that contains the messages + """ + self.select_mailbox(mbox, False) if start and stop: submessages = self.messages[start - 1:stop] range = ",".join(submessages) else: submessages = [start] range = start - if not all: - query = '(FLAGS BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])' - else: - query = '(RFC822)' + query = '(FLAGS BODY.PEEK[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])' data = self._cmd("FETCH", range, query) result = [] for uid in submessages: @@ -408,6 +440,48 @@ class IMAPconnector(object): result += [msg] return result + def fetchmail(self, mbox, mailid, fmt="plain"): + """Retrieve information about a specific message + + Issue a FETCH command to retrieve a message's content from the + server. In order to not overload the server, we first retrieve + the BODYSTRUCTURE of the message. Then, according to the + result and to the user's preferences, we retrieve the + appropriate content (plain, html, etc.). + + :param mbox: the mailbox containing the message + :param mailid: the message's unique id + :param fmt: the desired content's format + """ + self.select_mailbox(mbox, False) + data = self._cmd("FETCH", mailid, "(BODYSTRUCTURE BODY.PEEK[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])") + content = data[int(mailid)]['BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)]'] + + fallback_fmt = "html" if fmt == "plain" else "plain" + (pnum, encoding, size) = (None, None, 0) + for f in [fmt, fallback_fmt]: + pnum, encoding, size = \ + self.__find_content_in_bodystruct(data[int(mailid)]['BODYSTRUCTURE'], + "text", f) + if pnum is not None and size: + break + if pnum is None: + return None + + data = self._cmd("FETCH", mailid, "(BODY.PEEK[%s])" % pnum) + + data = data[int(mailid)]['BODY[%s]' % pnum] + if encoding == "base64": + import base64 + content += base64.b64decode(data) + elif encoding == "quoted-printable": + import quopri + content += quopri.decodestring(data) + else: + content += data + + return email.message_from_string(content) + def get_imapconnector(request): imapc = IMAPconnector(user=request.user.username, password=request.session["password"]) diff --git a/extensions/webmail/static/css/webmail.css b/extensions/webmail/static/css/webmail.css index ef221f3fc..7c9a7c60c 100644 --- a/extensions/webmail/static/css/webmail.css +++ b/extensions/webmail/static/css/webmail.css @@ -12,6 +12,14 @@ html > body #listing { bottom: 35px; } +body.loading { + display: block; + filter: alpha(opacity=50); + opacity: 0.5; + z-index: 3; + background: #FFF url("../../css/spinner.gif") center center no-repeat; +} + .clearfix:after { content: "."; display: block; diff --git a/extensions/webmail/static/js/webmail.js b/extensions/webmail/static/js/webmail.js index ec96978a5..09fd0c762 100644 --- a/extensions/webmail/static/js/webmail.js +++ b/extensions/webmail/static/js/webmail.js @@ -21,13 +21,13 @@ var Webmail = new Class({ this.rtimer = null; this.editorid = "id_body"; - this.init_menubar(); this.init_folders_buttons(); this.init_folders_browsing(); /* FIXME: à faire uniquement pour le mode pushState */ /*this.init_emails_table();*/ + current_anchor.disable_spinner(); current_anchor.register_callback("listmailbox", this.listmailbox_callback.bind(this)); current_anchor.register_callback("viewmail", this.viewmail_callback.bind(this)); @@ -35,20 +35,17 @@ var Webmail = new Class({ current_anchor.register_callback("reply", this.compose_callback.bind(this)); current_anchor.register_callback("replyall", this.compose_callback.bind(this)); current_anchor.register_callback("forward", this.compose_callback.bind(this)); - }, - /* - * Menubar initialization - */ - init_menubar: function() { - searchbox_init(); - $$("a[name=compose]").addEvent("click", this.compose_loader); - $$("a[name=mark-read]").addEvent("click", this.send_mark_request.bind(this)); - $$("a[name=mark-unread]").addEvent("click", this.send_mark_request.bind(this)); - $$("a[name=fdaction]").addEvent("click", function(evt) { - this.simple_request(evt.target.get("href"), {}); - evt.stop(); + this.gspinner = new Spinner("document.body", { + message: gettext("Loading, please wait..."), + 'class': "spinner-noop" + }); + this.gspinner.addEvent("hide", function(target) { + this.gspinner.destroy(); + delete(this.gspinner); + current_anchor.enable_spinner(); }.bind(this)); + this.gspinner.show(); }, send_mark_request: function(evt) { @@ -286,7 +283,7 @@ var Webmail = new Class({ this.init_draggables(); - $$("tbody>tr").addEvent("dblclick", viewmail); + $$("tbody>tr").addEvent("dblclick", this.viewmail_loader); }, /* @@ -405,7 +402,10 @@ var Webmail = new Class({ * */ page_update: function(response) { - + if (this.gspinner) { + this.gspinner.hide(); + } + updatelisting(response); }, /* @@ -437,16 +437,41 @@ var Webmail = new Class({ */ listmailbox_callback: function(resp) { window.removeEvents("resize"); - updatelisting(resp); + this.page_update(resp); + if (resp.menu != undefined) { + searchbox_init(); + $$("a[name=compose]").addEvent("click", this.compose_loader); + $$("a[name=mark-read]") + .addEvent("click", this.send_mark_request.bind(this)); + $$("a[name=mark-unread]") + .addEvent("click", this.send_mark_request.bind(this)); + $$("a[name=fdaction]").addEvent("click", function(evt) { + this.simple_request(evt.target.get("href"), {}); + evt.stop(); + }.bind(this)); + } this.init_emails_table(); }, + /* + * Loader of the 'viemmail' action + */ + viewmail_loader: function(evt) { + evt.stop(); + current_anchor.reset().setparams({ + action: "viewmail", + mailid: this.get("id") + }).update(); + }, + /* * Callback of the 'viewmail' action */ viewmail_callback: function(resp) { - wm_updatelisting(resp, "hidden"); - $$("a[name=back]").addEvent("click", this._listmailbox_loader); + /*wm_updatelisting(resp, "hidden");*/ + this.page_update(resp); + + //$$("a[name=back]").addEvent("click", this._listmailbox_loader); $$("a[name=reply]").addEvent("click", this.reply_loader); $$("a[name=replyall]").addEvent("click", this.replyall_loader); $$("a[name=forward]").addEvent("click", this.forward_loader); @@ -517,7 +542,7 @@ var Webmail = new Class({ */ compose_callback: function(resp) { //wm_updatelisting(resp); - updatelisting(resp); + this.page_update(resp); window.addEvent("resize", this.resize_window_callback.bind(this)); diff --git a/extensions/webmail/templates/webmail/index.html b/extensions/webmail/templates/webmail/index.html index 7c23f9e27..691b5141a 100644 --- a/extensions/webmail/templates/webmail/index.html +++ b/extensions/webmail/templates/webmail/index.html @@ -8,6 +8,7 @@ {{ block.super }} {% block css %}{% endblock %}