mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
wip: Webmail big refactoring and performance improvment
Using LSUB and LIST-EXTENDED.
This commit is contained in:
@@ -20,3 +20,9 @@ class WebmailError(Exception):
|
||||
def __str__(self):
|
||||
return self.reason
|
||||
|
||||
class ImapError(Exception):
|
||||
def __init__(self, reason):
|
||||
self.reason = reason
|
||||
|
||||
def __str__(self):
|
||||
return str(self.reason)
|
||||
|
||||
@@ -5,13 +5,43 @@
|
||||
"""
|
||||
import imaplib, ssl, email
|
||||
import re
|
||||
from functools import wraps
|
||||
from imapclient.response_parser import *
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.connections import *
|
||||
from modoboa.lib.webutils import static_url
|
||||
from exceptions import ImapError
|
||||
|
||||
class capability(object):
|
||||
"""
|
||||
Simple decorator to check if the server presents the required
|
||||
capability. If not, a fallback method is called instead.
|
||||
|
||||
:param name: the capability name (upper case)
|
||||
:param fallback_method: a method's name
|
||||
"""
|
||||
def __init__(self, name, fallback_method):
|
||||
self.name = name
|
||||
self.fallback_method = fallback_method
|
||||
|
||||
def __call__(self, method):
|
||||
@wraps(method)
|
||||
def wrapped_func(cls, *args, **kwargs):
|
||||
if self.name in cls.m.capabilities:
|
||||
return method(cls, *args, **kwargs)
|
||||
return getattr(cls, self.fallback_method)(*args, **kwargs)
|
||||
|
||||
return wrapped_func
|
||||
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = ConnectionsManager
|
||||
|
||||
list_base_pattern = r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" "(?P<name>[^"]*)"'
|
||||
list_response_pattern = re.compile(list_base_pattern)
|
||||
listextended_response_pattern = \
|
||||
re.compile(list_base_pattern + r'\s*(?P<childinfo>.*)')
|
||||
|
||||
def __init__(self, user=None, password=None):
|
||||
self.criterions = []
|
||||
self.address = parameters.get_admin("IMAP_SERVER")
|
||||
@@ -20,6 +50,39 @@ class IMAPconnector(object):
|
||||
if not status:
|
||||
raise Exception(msg)
|
||||
|
||||
def _cmd(self, name, *args):
|
||||
"""IMAP command wrapper
|
||||
|
||||
To simplify errors handling, this wrapper calls the
|
||||
appropriate method (``uid`` or FIXME) and then check the
|
||||
return code. If an error has occured, an ``ImapError``
|
||||
exception is raised.
|
||||
|
||||
For specific commands commands (FETCH, ...), the result is
|
||||
parsed using the IMAPclient module before being returned.
|
||||
|
||||
:param name: the command's name
|
||||
:return: the command's result
|
||||
"""
|
||||
if name in ['FETCH', 'SORT', 'STORE']:
|
||||
try:
|
||||
typ, data = self.m.uid(name, *args, **kwargs)
|
||||
except imaplib.IMAP4.error, e:
|
||||
raise ImapError(e)
|
||||
if typ == "NO":
|
||||
raise ImapError(data)
|
||||
return parse_fetch_response(data)
|
||||
|
||||
try:
|
||||
typ, data = self.m._simple_command(name, *args)
|
||||
except imaplib.IMAP4.error, e:
|
||||
raise ImapError(e)
|
||||
if typ == "NO":
|
||||
raise ImapError(data)
|
||||
if not name in self.m.untagged_responses:
|
||||
return None
|
||||
return self.m.untagged_responses.pop(name)
|
||||
|
||||
def refresh(self, user, password):
|
||||
"""Check if current connection needs a refresh
|
||||
|
||||
@@ -113,12 +176,11 @@ class IMAPconnector(object):
|
||||
sdescr["class"] = "subfolders"
|
||||
return True
|
||||
|
||||
def _listfolders(self, topfolder='INBOX', md_folders=[]):
|
||||
list_response_pattern = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
|
||||
def _listfolders_simple(self, topmailbox='INBOX', md_mailboxes=[]):
|
||||
(status, data) = self.m.list()
|
||||
result = []
|
||||
for mb in data:
|
||||
flags, delimiter, name = list_response_pattern.match(mb).groups()
|
||||
flags, delimiter, name = self.list_response_pattern.match(mb).groups()
|
||||
name = name.strip('"').decode("imap4-utf-7")
|
||||
if re.search("\%s" % delimiter, name):
|
||||
parts = name.split(".")
|
||||
@@ -131,7 +193,7 @@ class IMAPconnector(object):
|
||||
continue
|
||||
present = False
|
||||
descr = {"name" : name}
|
||||
for mdf in md_folders:
|
||||
for mdf in md_mailboxes:
|
||||
if mdf["name"] == name:
|
||||
present = True
|
||||
break
|
||||
@@ -140,23 +202,34 @@ class IMAPconnector(object):
|
||||
from operator import itemgetter
|
||||
return sorted(result, key=itemgetter("name"))
|
||||
|
||||
@capability('LIST-EXTENDED', '_listfolders_simple')
|
||||
def _listfolders(self, topmailbox='', md_mailboxes=[]):
|
||||
resp = self._cmd("LSUB", topmailbox, "%", "RETURN", "(CHILDREN)")
|
||||
print resp
|
||||
for mb in resp:
|
||||
flags, delimiter, name, childinfo = \
|
||||
self.listextended_response_pattern.match(mb).groups()
|
||||
flags = flags.split(' ')
|
||||
print flags
|
||||
return []
|
||||
|
||||
def getfolders(self, user, unseen_messages=True):
|
||||
md_folders = [{"name" : "INBOX", "class" : "inbox"},
|
||||
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class" : "drafts"},
|
||||
{"name" : 'Junk'},
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER")},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "trash"}]
|
||||
md_folders += self._listfolders(md_folders=md_folders)
|
||||
md_mailboxes = [{"name" : "INBOX", "class" : "inbox"},
|
||||
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
|
||||
"class" : "drafts"},
|
||||
{"name" : 'Junk'},
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER")},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "trash"}]
|
||||
md_mailboxes += self._listfolders(md_mailboxes=md_mailboxes)
|
||||
if unseen_messages:
|
||||
for fd in md_folders:
|
||||
for fd in md_mailboxes:
|
||||
key = fd.has_key("path") and "path" or "name"
|
||||
count = self.unseen_messages(fd[key])
|
||||
if count == 0:
|
||||
continue
|
||||
fd["unseen"] = count
|
||||
return md_folders
|
||||
return md_mailboxes
|
||||
|
||||
def _add_flag(self, folder, mail_id, flag):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
# coding: utf-8
|
||||
|
||||
from modoboa.lib.webutils import ajax_response
|
||||
from modoboa.extensions.webmail.lib import WebmailError
|
||||
from modoboa.lib.webutils import ajax_response, _render_error
|
||||
from modoboa.extensions.webmail.exceptions import *
|
||||
|
||||
class WebmailErrorMiddleware(object):
|
||||
def process_exception(self, request, exception):
|
||||
if not isinstance(exception, WebmailError):
|
||||
if not isinstance(exception, (WebmailError, ImapError)):
|
||||
return None
|
||||
return ajax_response(request, status="ko",
|
||||
respmsg=str(exception), norefresh=True)
|
||||
json = request.GET.get("json", False) if request.method == "GET" \
|
||||
else request.POST.get("json", False)
|
||||
if json:
|
||||
return ajax_response(request, status="ko",
|
||||
respmsg=str(exception), norefresh=True)
|
||||
return _render_error(request, user_context=dict(error=str(exception)))
|
||||
|
||||
@@ -23,8 +23,12 @@ var Webmail = new Class({
|
||||
this.init_menubar();
|
||||
this.init_folders_buttons();
|
||||
this.init_folders_browsing();
|
||||
this.init_emails_table();
|
||||
|
||||
/* FIXME: à faire uniquement pour le mode pushState */
|
||||
/*this.init_emails_table();*/
|
||||
|
||||
current_anchor.register_callback("listmailbox",
|
||||
this.listmailbox_callback.bind(this));
|
||||
current_anchor.register_callback("viewmail", this.viewmail_callback.bind(this));
|
||||
current_anchor.register_callback("compose", this.compose_callback.bind(this));
|
||||
current_anchor.register_callback("reply", this.compose_callback.bind(this));
|
||||
@@ -339,8 +343,10 @@ var Webmail = new Class({
|
||||
/*
|
||||
* Callback for 'folder' action
|
||||
*/
|
||||
folder_callback: function(resp) {
|
||||
|
||||
listmailbox_callback: function(resp) {
|
||||
window.removeEvents("resize");
|
||||
updatelisting(resp);
|
||||
this.init_emails_table();
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.contrib import messages
|
||||
from modoboa.admin.models import Mailbox
|
||||
from modoboa.lib import parameters
|
||||
from modoboa.lib.webutils import _render, _render_error, \
|
||||
getctx, _render_to_string, ajax_response
|
||||
getctx, _render_to_string, ajax_response, ajax_simple_response
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.lib.email_listing import parse_search_parameters, Paginator
|
||||
from modoboa.admin.lib import is_not_localadmin
|
||||
@@ -516,7 +516,7 @@ def listmailbox(request):
|
||||
lst = ImapListing(request.user, request.session["password"],
|
||||
baseurl=mbox, folder=mbox, order="-date")
|
||||
|
||||
return dict(listing=lst.render(request))
|
||||
return lst.render(request)
|
||||
|
||||
@login_required
|
||||
@is_not_localadmin()
|
||||
@@ -561,3 +561,6 @@ def newindex(request):
|
||||
menufunc = globals()["listmailbox_menu"]
|
||||
response["menu"] = menufunc("", request.session["mbox"], request.user)
|
||||
return _render(request, "webmail/index.html", response)
|
||||
|
||||
response.update(status="ok", callback=action)
|
||||
return ajax_simple_response(response)
|
||||
|
||||
+30
-22
@@ -123,29 +123,37 @@ class EmailListing(object):
|
||||
</form>""")
|
||||
return tpl.render(Context({"table" : table.render()}))
|
||||
|
||||
# def render(self, request, pageid=1, **kwargs):
|
||||
# listing = ""
|
||||
# page = None
|
||||
# if not self.empty:
|
||||
# page = self.paginator.getpage(pageid)
|
||||
# if not page:
|
||||
# listing = _("This folder contains no messages")
|
||||
# else:
|
||||
# listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
# elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
|
||||
# context = {
|
||||
# "listing" : listing,
|
||||
# "elapsed" : elapsed,
|
||||
# "navbar" : self.render_navbar(page),
|
||||
# "selection" : self.folder,
|
||||
# "navparams" : self.navparams,
|
||||
# "deflocation" : self.deflocation,
|
||||
# "defcallback" : self.defcallback,
|
||||
# "reset_wm_url" : self.reset_wm_url
|
||||
# }
|
||||
# for k, v in self.extravars.iteritems():
|
||||
# context[k] = v
|
||||
# return _render(request, self.tpl, context)
|
||||
|
||||
def render(self, request, pageid=1, **kwargs):
|
||||
listing = ""
|
||||
page = None
|
||||
if not self.empty:
|
||||
page = self.paginator.getpage(pageid)
|
||||
if not page:
|
||||
listing = _("This folder contains no messages")
|
||||
else:
|
||||
listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
|
||||
context = {
|
||||
"listing" : listing,
|
||||
"elapsed" : elapsed,
|
||||
"navbar" : self.render_navbar(page),
|
||||
"selection" : self.folder,
|
||||
"navparams" : self.navparams,
|
||||
"deflocation" : self.deflocation,
|
||||
"defcallback" : self.defcallback,
|
||||
"reset_wm_url" : self.reset_wm_url
|
||||
}
|
||||
for k, v in self.extravars.iteritems():
|
||||
context[k] = v
|
||||
return _render(request, self.tpl, context)
|
||||
page = self.paginator.getpage(pageid)
|
||||
if not page:
|
||||
listing = _("This folder contains no message")
|
||||
else:
|
||||
listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
return dict(listing=listing, navbar=self.render_navbar(page))
|
||||
|
||||
def parse_search_parameters(request):
|
||||
if request.GET.has_key("pattern"):
|
||||
|
||||
@@ -217,6 +217,6 @@ var AnchorNavigation = new Class({
|
||||
$(document.body).set("html", xhr.responseText);
|
||||
$(document.body).setStyle("overflow", "auto");
|
||||
}
|
||||
}).get();
|
||||
}).get("json=1");
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user