wip: webmail big refactoring and perfomance improvment

Misc. changes.
This commit is contained in:
Antoine Nguyen
2011-12-01 17:36:21 +01:00
parent b9061b7f4a
commit c7cfac8e0a
13 changed files with 251 additions and 93 deletions
+81 -7
View File
@@ -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"])
@@ -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;
+44 -19
View File
@@ -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));
@@ -8,6 +8,7 @@
<script type="text/javascript" src="{{ MEDIA_URL }}webmail/js/webmail.js"></script>
{{ block.super }}
<script type="text/javascript">
function wm_updatelisting(resp) {
window.removeEvents("resize");
updatelisting(resp);
+6 -4
View File
@@ -11,10 +11,10 @@ from modoboa.lib.webutils import static_url
register = template.Library()
@register.simple_tag
def viewm_menu(selection, backurl, folder, mail_id, user):
def viewmail_menu(selection, folder, user, mail_id=None):
entries = [
{"name" : "back",
"url" : backurl,
"url" : "javascript:history.go(-1);",
"img" : static_url("pics/back.png"),
"label" : _("Back")},
{"name" : "reply",
@@ -40,10 +40,12 @@ def viewm_menu(selection, backurl, folder, mail_id, user):
"menu" : [
{"name" : "activate_links",
"label" : _("Activate links"),
"url" : reverse(webmail.views.viewmail, args=[folder, mail_id]) + "?links=1"},
"url" : ""},
#reverse(webmail.views.viewmail, args=[folder, mail_id]) + "?links=1"},
{"name" : "disable_links",
"label" : _("Disable links"),
"url" : reverse(webmail.views.viewmail, args=[folder, mail_id]) + "?links=0"},
"url" : ""}
#reverse(webmail.views.viewmail, args=[folder, mail_id]) + "?links=0"},
]
}
]
+4 -2
View File
@@ -9,6 +9,8 @@ urlpatterns = patterns('modoboa.extensions.webmail.views',
#(r'^$', "index"),
(r'^$', "newindex"),
(r'^submailboxes', "submailboxes"),
(r'^mailcontent', "getmailcontent"),
# (r'^compose/$', "compose"),
(r'^move/$', "move"),
@@ -24,9 +26,9 @@ urlpatterns = patterns('modoboa.extensions.webmail.views',
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/part/$', 'getattachment'),
(r'^(?P<fdname>.+)/(?P<mail_id>[\w\-\+]+)/delete/$', 'delete'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
#(r'^(?P<folder>.+)/(?P<mailid>[\w\-\+]+)/content/$', 'getmailcontent'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/forward/$', 'forward'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/reply/$', 'reply'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
# (r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
(r'^(?P<name>.+)/$', "folder"),
)
+69 -51
View File
@@ -19,7 +19,7 @@ from modoboa.admin.lib import is_not_localadmin
from modoboa.auth.lib import *
from lib import *
from forms import *
from templatetags.webextras import *
from templatetags import webextras
def __render_common_components(request, folder_name, lst=None, content=None, menu=None):
"""Render all components that are common to all pages in the webmail
@@ -124,51 +124,58 @@ def index(request):
return _render_error(request, user_context={"error" : exp})
return lst.render(request)
def fetchmail(request, folder, mail_id, all=False):
res = IMAPconnector(user=request.user.username,
password=request.session["password"]).fetch(start=mail_id,
folder=folder,
all=all)
if len(res):
return res[0]
return None
def fetchmail(request, mbox, mailid, all=False):
imapc = get_imapconnector(request)
res = imapc.fetchmail(mbox, mailid)
return res
# res = IMAPconnector(user=request.user.username,
# password=request.session["password"]).fetch(start=mail_id,
# folder=folder,
# all=all)
# if len(res):
# return res[0]
# return None
# @login_required
# @is_not_localadmin()
# def viewmail(request, folder, mail_id=None):
# from templatetags.webextras import viewm_menu
# if request.GET.has_key("links"):
# links = int(request.GET["links"])
# else:
# links = parameters.get_user(request.user, "ENABLE_LINKS") == "yes" and 1 or 0
# url = reverse(getmailcontent, args=[folder, mail_id]) + ("?links=%d" % links)
# content = Template("""
# <iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
# """).render(Context({"url" : url}))
# menu = viewm_menu("", get_current_url(request), folder, mail_id,
# request.user.get_all_permissions())
# mbc = IMAPconnector(user=request.user.username,
# password=request.session["password"])
# ctx = getctx("ok", **__render_common_components(request, folder,
# menu=menu, content=content))
# return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
def viewmail(request, folder, mail_id=None):
from templatetags.webextras import viewm_menu
def getmailcontent(request):
mbox = request.GET.get("mbox", None)
mailid = request.GET.get("mailid", None)
if mbox is None or mailid is None:
raise WebmailError(_("Invalid request"))
if request.GET.has_key("links"):
links = int(request.GET["links"])
else:
links = parameters.get_user(request.user, "ENABLE_LINKS") == "yes" and 1 or 0
url = reverse(getmailcontent, args=[folder, mail_id]) + ("?links=%d" % links)
content = Template("""
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url" : url}))
menu = viewm_menu("", get_current_url(request), folder, mail_id,
request.user.get_all_permissions())
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = getctx("ok", **__render_common_components(request, folder,
menu=menu, content=content))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
content = fetchmail(request, mbox, mailid, True)
@login_required
@is_not_localadmin()
def getmailcontent(request, folder, mail_id):
msg = fetchmail(request, folder, mail_id, True)
if "class" in msg.keys() and msg["class"] == "unseen":
IMAPconnector(user=request.user.username,
password=request.session["password"]).msg_read(folder, mail_id)
email = ImapEmail(msg, request.user, links=request.GET["links"])
try:
pageid = request.session["page"]
except KeyError:
pageid = "1"
# if "class" in msg.keys() and msg["class"] == "unseen":
# IMAPconnector(user=request.user.username,
# password=request.session["password"]).msg_read(folder, mail_id)
email = ImapEmail(content, request.user, links=request.GET["links"])
return _render(request, "common/viewmail.html", {
"headers" : email.render_headers(folder=folder, mail_id=mail_id),
"folder" : folder, "imapid" : mail_id, "mailbody" : email.body
"headers" : email.render_headers(folder=mbox, mail_id=mailid),
"folder" : mbox, "imapid" : mailid, "mailbody" : email.body
})
@login_required
@@ -603,6 +610,20 @@ def compose(request):
form.fields["from_"].initial = request.user.username
return render_compose(request, form, "compose", insert_signature=True)
def viewmail(request):
mailid = request.GET.get("mailid", None)
if mailid is None:
raise WebmailError(_("Invalid request"))
links = 1
url = reverse(getmailcontent) + "?mbox=%s&mailid=%s&links=%d" % \
(request.session["mbox"], mailid, links)
content = Template("""
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url" : url}))
return dict(listing=content, menuargs=dict(mail_id=mailid))
@login_required
@is_not_localadmin()
def submailboxes(request):
@@ -650,24 +671,21 @@ def newindex(request):
curmbox = request.session.get("mbox", "INBOX")
if not json:
request.session["lastaction"] = None
imapc = get_imapconnector(request)
response["refreshrate"] = \
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
response["mboxes"] = render_mboxes_list(request, imapc)
try:
menufunc = globals()["%s_menu" % action]
except KeyError:
if request.session.has_key("lastaction"):
menufunc = globals()["%s_menu" % request.session["lastaction"]]
else:
menufunc = globals()["listmailbox_menu"]
response["menu"] = menufunc("", curmbox, request.user)
return _render(request, "webmail/index.html", response)
if request.session.get("lastaction", None) != action:
request.session["lastaction"] = action
if request.session["lastaction"] != action:
extra_args = {}
if response.has_key("menuargs"):
extra_args = response["menuargs"]
del response["menuargs"]
try:
response["menu"] = globals()["%s_menu" % action]("", curmbox, request.user)
response["menu"] = \
getattr(webextras, "%s_menu" % action)("", curmbox, request.user, **extra_args)
except KeyError:
pass
+1 -1
View File
@@ -115,7 +115,7 @@ class EmailListing(object):
def fetch(self, request, id_start, id_stop):
table = self.tbltype(request,
self.mbc.fetch(start=id_start, stop=id_stop,
folder=self.folder,
mbox=self.folder,
nbelems=self.elems_per_page))
tpl = Template("""
<form method="POST" id="listingform">
+1 -1
View File
@@ -220,7 +220,7 @@ div.right {
.logo {
float: right;
/*margin: 5px 0 0 5px;*/
height: 90px;
height: 80px;
}
.dropdown {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

+32 -7
View File
@@ -9,7 +9,9 @@ var AnchorNavigation = new Class({
checkinterval: 300,
defloadingtext: gettext("Loading..."),
defloadingcolor: "gray",
defcallback: null
defcallback: null,
spin_disabled: false,
spin_target: undefined
},
initialize: function(deflocation, options) {
@@ -29,6 +31,17 @@ var AnchorNavigation = new Class({
}
},
/*
* Disable the spinner.
*/
disable_spinner: function() {
this.options.spin_disabled = true;
},
enable_spinner: function() {
this.options.spin_disabled = false;
},
reset_loading_infos: function() {
this.loading_message = this.options.defloadingtext;
this.loading_color = this.options.defloadingcolor;
@@ -200,22 +213,34 @@ var AnchorNavigation = new Class({
var query = this.serialized.substring(1);
infobox.show(this.loading_message, {
profile: this.loading_color,
spinner: true
});
if (!this.options.spin_disabled) {
var target = this.options.spin_target ? this.options.spin_target
: document.body;
$(target).spin({
message: this.loading_message
});
/*infobox.show(this.loading_message, {
profile: this.loading_color,
spinner: true
});*/
}
new Request.JSON({
url: query,
noCache : true,
onSuccess: function(resp) {
if (!this.options.spin_disabled) {
var target = this.options.spin_target ? this.options.spin_target
: document.body;
$(target).unspin();
}
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"));
infobox.hide(1);
/*infobox.info(gettext("Done"));
infobox.hide(1);*/
this.reset_loading_infos();
}.bind(this),
onFailure: function(xhr) {
+3 -1
View File
@@ -8,7 +8,9 @@
<script type="text/javascript" src="{{ MEDIA_URL }}js/email_listing.js"></script>
{% block css %}{% endblock %}
<script type="text/javascript">
var current_anchor = new AnchorNavigation("{{ deflocation }}");
var current_anchor = new AnchorNavigation("{{ deflocation }}", {
spin_target: "page"
});
function viewmail(event) {
event.stop();
+1
View File
@@ -9,6 +9,7 @@
<link rel="icon" type="image/png" href="{{ MEDIA_URL }}css/logo-icon.png" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/default.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/SqueezeBox.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/spinner.css" />
<script src="{{ MEDIA_URL }}js/mootools.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/mootools-more.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/SqueezeBox.js" type="text/javascript"></script>