summaryrefslogtreecommitdiff
path: root/src/leap/mail/imap/account.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/mail/imap/account.py')
-rw-r--r--src/leap/mail/imap/account.py253
1 files changed, 191 insertions, 62 deletions
diff --git a/src/leap/mail/imap/account.py b/src/leap/mail/imap/account.py
index 70ed13b..fe466cb 100644
--- a/src/leap/mail/imap/account.py
+++ b/src/leap/mail/imap/account.py
@@ -22,6 +22,7 @@ import logging
import os
import time
+from twisted.internet import defer
from twisted.mail import imap4
from twisted.python import log
from zope.interface import implements
@@ -65,6 +66,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
_soledad = None
selected = None
closed = False
+ _initialized = False
def __init__(self, account_name, soledad, memstore=None):
"""
@@ -93,14 +95,39 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
self.__mailboxes = set([])
- self.initialize_db()
+ self._deferred_initialization = defer.Deferred()
+ self._initialize_storage()
- # every user should have the right to an inbox folder
- # at least, so let's make one!
- self._load_mailboxes()
+ def _initialize_storage(self):
- if not self.mailboxes:
- self.addMailbox(self.INBOX_NAME)
+ def add_mailbox_if_none(result):
+ # every user should have the right to an inbox folder
+ # at least, so let's make one!
+ if not self.mailboxes:
+ self.addMailbox(self.INBOX_NAME)
+
+ def finish_initialization(result):
+ self._initialized = True
+ self._deferred_initialization.callback(None)
+
+ def load_mbox_cache(result):
+ d = self._load_mailboxes()
+ d.addCallback(lambda _: result)
+ return d
+
+ d = self.initialize_db()
+
+ d.addCallback(load_mbox_cache)
+ d.addCallback(add_mailbox_if_none)
+ d.addCallback(finish_initialization)
+
+ def callWhenReady(self, cb):
+ if self._initialized:
+ cb(self)
+ return defer.succeed(None)
+ else:
+ self._deferred_initialization.addCallback(cb)
+ return self._deferred_initialization
def _get_empty_mailbox(self):
"""
@@ -120,10 +147,14 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
:rtype: SoledadDocument
"""
# XXX use soledadstore instead ...;
- doc = self._soledad.get_from_index(
+ def get_first_if_any(docs):
+ return docs[0] if docs else None
+
+ d = self._soledad.get_from_index(
self.TYPE_MBOX_IDX, self.MBOX_KEY,
self._parse_mailbox_name(name))
- return doc[0] if doc else None
+ d.addCallback(get_first_if_any)
+ return d
@property
def mailboxes(self):
@@ -134,19 +165,12 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
return sorted(self.__mailboxes)
def _load_mailboxes(self):
- self.__mailboxes.update(
- [doc.content[self.MBOX_KEY]
- for doc in self._soledad.get_from_index(
- self.TYPE_IDX, self.MBOX_KEY)])
-
- @property
- def subscriptions(self):
- """
- A list of the current subscriptions for this account.
- """
- return [doc.content[self.MBOX_KEY]
- for doc in self._soledad.get_from_index(
- self.TYPE_SUBS_IDX, self.MBOX_KEY, '1')]
+ def update_mailboxes(db_indexes):
+ self.__mailboxes.update(
+ [doc.content[self.MBOX_KEY] for doc in db_indexes])
+ d = self._soledad.get_from_index(self.TYPE_IDX, self.MBOX_KEY)
+ d.addCallback(update_mailboxes)
+ return d
def getMailbox(self, name):
"""
@@ -182,7 +206,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
one is provided.
:type creation_ts: int
- :returns: True if successful
+ :returns: a Deferred that will contain the document if successful.
:rtype: bool
"""
name = self._parse_mailbox_name(name)
@@ -203,21 +227,29 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
mbox[self.MBOX_KEY] = name
mbox[self.CREATED_KEY] = creation_ts
- doc = self._soledad.create_doc(mbox)
- self._load_mailboxes()
- return bool(doc)
+ def load_mbox_cache(result):
+ d = self._load_mailboxes()
+ d.addCallback(lambda _: result)
+ return d
+
+ d = self._soledad.create_doc(mbox)
+ d.addCallback(load_mbox_cache)
+ return d
def create(self, pathspec):
"""
Create a new mailbox from the given hierarchical name.
- :param pathspec: The full hierarchical name of a new mailbox to create.
- If any of the inferior hierarchical names to this one
- do not exist, they are created as well.
+ :param pathspec:
+ The full hierarchical name of a new mailbox to create.
+ If any of the inferior hierarchical names to this one
+ do not exist, they are created as well.
:type pathspec: str
- :return: A true value if the creation succeeds.
- :rtype: bool
+ :return:
+ A deferred that will fire with a true value if the creation
+ succeeds.
+ :rtype: Deferred
:raise MailboxException: Raised if this mailbox cannot be added.
"""
@@ -225,18 +257,43 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
paths = filter(
None,
self._parse_mailbox_name(pathspec).split('/'))
+
+ subs = []
+ sep = '/'
+
for accum in range(1, len(paths)):
try:
- self.addMailbox('/'.join(paths[:accum]))
+ partial = sep.join(paths[:accum])
+ d = self.addMailbox(partial)
+ subs.append(d)
except imap4.MailboxCollision:
pass
try:
- self.addMailbox('/'.join(paths))
+ df = self.addMailbox(sep.join(paths))
except imap4.MailboxCollision:
if not pathspec.endswith('/'):
- return False
- self._load_mailboxes()
- return True
+ df = defer.succeed(False)
+ else:
+ df = defer.succeed(True)
+ finally:
+ subs.append(df)
+
+ def all_good(result):
+ return all(result)
+
+ def load_mbox_cache(result):
+ d = self._load_mailboxes()
+ d.addCallback(lambda _: result)
+ return d
+
+ if subs:
+ d1 = defer.gatherResults(subs, consumeErrors=True)
+ d1.addCallback(load_mbox_cache)
+ d1.addCallback(all_good)
+ else:
+ d1 = defer.succeed(False)
+ d1.addCallback(load_mbox_cache)
+ return d1
def select(self, name, readwrite=1):
"""
@@ -275,17 +332,20 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
:param name: the mailbox to be deleted
:type name: str
- :param force: if True, it will not check for noselect flag or inferior
- names. use with care.
+ :param force:
+ if True, it will not check for noselect flag or inferior
+ names. use with care.
:type force: bool
+ :rtype: Deferred
"""
name = self._parse_mailbox_name(name)
if name not in self.mailboxes:
- raise imap4.MailboxException("No such mailbox: %r" % name)
+ err = imap4.MailboxException("No such mailbox: %r" % name)
+ return defer.fail(err)
mbox = self.getMailbox(name)
- if force is False:
+ if not force:
# See if this box is flagged \Noselect
# XXX use mbox.flags instead?
mbox_flags = mbox.getFlags()
@@ -294,11 +354,12 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
# as part of their root.
for others in self.mailboxes:
if others != name and others.startswith(name):
- raise imap4.MailboxException, (
+ err = imap4.MailboxException(
"Hierarchically inferior mailboxes "
"exist and \\Noselect is set")
+ return defer.fail(err)
self.__mailboxes.discard(name)
- mbox.destroy()
+ return mbox.destroy()
# XXX FIXME --- not honoring the inferior names...
@@ -331,14 +392,30 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
if new in self.mailboxes:
raise imap4.MailboxCollision(repr(new))
+ rename_deferreds = []
+
+ def load_mbox_cache(result):
+ d = self._load_mailboxes()
+ d.addCallback(lambda _: result)
+ return d
+
+ def update_mbox_doc_name(mbox, oldname, newname, update_deferred):
+ mbox.content[self.MBOX_KEY] = newname
+ d = self._soledad.put_doc(mbox)
+ d.addCallback(lambda r: update_deferred.callback(True))
+
for (old, new) in inferiors:
- self._memstore.rename_fdocs_mailbox(old, new)
- mbox = self._get_mailbox_by_name(old)
- mbox.content[self.MBOX_KEY] = new
self.__mailboxes.discard(old)
- self._soledad.put_doc(mbox)
+ self._memstore.rename_fdocs_mailbox(old, new)
+
+ d0 = defer.Deferred()
+ d = self._get_mailbox_by_name(old)
+ d.addCallback(update_mbox_doc_name, old, new, d0)
+ rename_deferreds.append(d0)
- self._load_mailboxes()
+ d1 = defer.gatherResults(rename_deferreds, consumeErrors=True)
+ d1.addCallback(load_mbox_cache)
+ return d1
def _inferiorNames(self, name):
"""
@@ -354,6 +431,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
inferiors.append(infname)
return inferiors
+ # TODO ------------------ can we preserve the attr?
+ # maybe add to memory store.
def isSubscribed(self, name):
"""
Returns True if user is subscribed to this mailbox.
@@ -361,10 +440,35 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
:param name: the mailbox to be checked.
:type name: str
- :rtype: bool
+ :rtype: Deferred (will fire with bool)
+ """
+ subscribed = self.SUBSCRIBED_KEY
+
+ def is_subscribed(mbox):
+ subs_bool = bool(mbox.content.get(subscribed, False))
+ return subs_bool
+
+ d = self._get_mailbox_by_name(name)
+ d.addCallback(is_subscribed)
+ return d
+
+ # TODO ------------------ can we preserve the property?
+ # maybe add to memory store.
+
+ def _get_subscriptions(self):
"""
- mbox = self._get_mailbox_by_name(name)
- return mbox.content.get('subscribed', False)
+ Return a list of the current subscriptions for this account.
+
+ :returns: A deferred that will fire with the subscriptions.
+ :rtype: Deferred
+ """
+ def get_docs_content(docs):
+ return [doc.content[self.MBOX_KEY] for doc in docs]
+
+ d = self._soledad.get_from_index(
+ self.TYPE_SUBS_IDX, self.MBOX_KEY, '1')
+ d.addCallback(get_docs_content)
+ return d
def _set_subscription(self, name, value):
"""
@@ -376,26 +480,42 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
:param value: the boolean value
:type value: bool
"""
+ # XXX Note that this kind of operation has
+ # no guarantees of atomicity. We should not be accessing mbox
+ # documents concurrently.
+
+ subscribed = self.SUBSCRIBED_KEY
+
+ def update_subscribed_value(mbox):
+ mbox.content[subscribed] = value
+ return self._soledad.put_doc(mbox)
+
# maybe we should store subscriptions in another
# document...
if name not in self.mailboxes:
- self.addMailbox(name)
- mbox = self._get_mailbox_by_name(name)
-
- if mbox:
- mbox.content[self.SUBSCRIBED_KEY] = value
- self._soledad.put_doc(mbox)
+ d = self.addMailbox(name)
+ d.addCallback(lambda v: self._get_mailbox_by_name(name))
+ else:
+ d = self._get_mailbox_by_name(name)
+ d.addCallback(update_subscribed_value)
+ return d
def subscribe(self, name):
"""
- Subscribe to this mailbox
+ Subscribe to this mailbox if not already subscribed.
:param name: name of the mailbox
:type name: str
+ :rtype: Deferred
"""
name = self._parse_mailbox_name(name)
- if name not in self.subscriptions:
- self._set_subscription(name, True)
+
+ def check_and_subscribe(subscriptions):
+ if name not in subscriptions:
+ return self._set_subscription(name, True)
+ d = self._get_subscriptions()
+ d.addCallback(check_and_subscribe)
+ return d
def unsubscribe(self, name):
"""
@@ -403,12 +523,21 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB, MBoxParser):
:param name: name of the mailbox
:type name: str
+ :rtype: Deferred
"""
name = self._parse_mailbox_name(name)
- if name not in self.subscriptions:
- raise imap4.MailboxException(
- "Not currently subscribed to %r" % name)
- self._set_subscription(name, False)
+
+ def check_and_unsubscribe(subscriptions):
+ if name not in subscriptions:
+ raise imap4.MailboxException(
+ "Not currently subscribed to %r" % name)
+ return self._set_subscription(name, False)
+ d = self._get_subscriptions()
+ d.addCallback(check_and_unsubscribe)
+ return d
+
+ def getSubscriptions(self):
+ return self._get_subscriptions()
def listMailboxes(self, ref, wildcard):
"""