Exemple #1
0
    def get_collection(self,
                       mbox_collection=True,
                       mbox_name=None,
                       mbox_uuid=None):
        """
        Get a collection for tests.
        """
        adaptor = SoledadMailAdaptor()
        store = self._soledad
        adaptor.store = store

        if mbox_collection:
            mbox_indexer = MailboxIndexer(store)
            mbox_name = mbox_name or "TestMbox"
            mbox_uuid = mbox_uuid or str(uuid.uuid4())
        else:
            mbox_indexer = mbox_name = None

        def get_collection_from_mbox_wrapper(wrapper):
            wrapper.uuid = mbox_uuid
            return MessageCollection(adaptor,
                                     store,
                                     mbox_indexer=mbox_indexer,
                                     mbox_wrapper=wrapper)

        d = adaptor.initialize_store(store)
        if mbox_collection:
            d.addCallback(lambda _: mbox_indexer.create_table(mbox_uuid))
        d.addCallback(lambda _: adaptor.get_or_create_mbox(store, mbox_name))
        d.addCallback(get_collection_from_mbox_wrapper)
        return d
Exemple #2
0
    def __init__(self, store, user_id, ready_cb=None):
        self.store = store
        self.user_id = user_id
        self.adaptor = self.adaptor_class()

        self.mbox_indexer = MailboxIndexer(self.store)

        # This flag is only used from the imap service for the moment.
        # In the future, we should prevent any public method to continue if
        # this is set to True. Also, it would be good to plug to the
        # authentication layer.
        self.session_ended = False

        self.deferred_initialization = defer.Deferred()
        self._ready_cb = ready_cb

        self._init_d = self._initialize_storage()
        self._initialize_sync_hooks()
Exemple #3
0
        def getCollection(_):
            adaptor = SoledadMailAdaptor()
            store = self._soledad
            adaptor.store = store
            mbox_indexer = MailboxIndexer(store)
            mbox_name = "INBOX"
            mbox_uuid = str(uuid.uuid4())

            def get_collection_from_mbox_wrapper(wrapper):
                wrapper.uuid = mbox_uuid
                return MessageCollection(
                    adaptor, store,
                    mbox_indexer=mbox_indexer, mbox_wrapper=wrapper)

            d = adaptor.initialize_store(store)
            d.addCallback(lambda _: mbox_indexer.create_table(mbox_uuid))
            d.addCallback(
                lambda _: adaptor.get_or_create_mbox(store, mbox_name))
            d.addCallback(get_collection_from_mbox_wrapper)
            return d
Exemple #4
0
class Account(object):
    """
    Account is the top level abstraction to access collections of messages
    associated with a LEAP Mail Account.

    It primarily handles creation and access of Mailboxes, which will be the
    basic collection handled by traditional MUAs, but it can also handle other
    types of Collections (tag based, for instance).

    leap.bitmask.mail.imap.IMAPAccount partially proxies methods in this
    class.
    """

    # Adaptor is passed to the returned MessageCollections, so if you want to
    # use a different adaptor this is the place to change it, by subclassing
    # the Account class.

    adaptor_class = SoledadMailAdaptor

    # this is a defaultdict, indexed by userid, that returns a
    # WeakValueDictionary mapping to collection instances so that we always
    # return a reference to them instead of creating new ones. however,
    # being a dictionary of weakrefs values, they automagically vanish
    # from the dict when no hard refs is left to them (so they can be
    # garbage collected) this is important because the different wrappers
    # rely on several kinds of deferredlocks that are kept as class or
    # instance variables.

    # We need it to be a class property because we create more than one Account
    # object in the current usage pattern (ie, one in the mail service, and
    # another one in the IncomingMailService). When we move to a proper service
    # tree we can let it be an instance attribute.
    _collection_mapping = defaultdict(weakref.WeakValueDictionary)

    def __init__(self, store, user_id, ready_cb=None):
        self.store = store
        self.user_id = user_id
        self.adaptor = self.adaptor_class()

        self.mbox_indexer = MailboxIndexer(self.store)

        # This flag is only used from the imap service for the moment.
        # In the future, we should prevent any public method to continue if
        # this is set to True. Also, it would be good to plug to the
        # authentication layer.
        self.session_ended = False

        self.deferred_initialization = defer.Deferred()
        self._ready_cb = ready_cb

        self._init_d = self._initialize_storage()
        self._initialize_sync_hooks()

    @defer.inlineCallbacks
    def _initialize_storage(self):
        yield self.adaptor.initialize_store(self.store)
        mboxes = yield self.list_all_mailbox_names()
        if INBOX_NAME not in mboxes:
            yield self.add_mailbox(INBOX_NAME)

        # This is so that we create the mboxes before Pixelated tries
        # to do it.

        if 'Sent' not in mboxes:
            yield self.add_mailbox('Sent')

        self.deferred_initialization.callback(None)
        if self._ready_cb is not None:
            self._ready_cb()

    def callWhenReady(self, cb, *args, **kw):
        """
        Execute the callback when the initialization of the Account is ready.
        Note that the callback will receive a first meaningless parameter.
        """
        self.deferred_initialization.addCallback(cb, *args, **kw)
        return self.deferred_initialization

    # Sync hooks

    def _initialize_sync_hooks(self):
        soledad_sync_hooks.post_sync_uid_reindexer.set_account(self)

    def _teardown_sync_hooks(self):
        soledad_sync_hooks.post_sync_uid_reindexer.set_account(None)

    #
    # Public API Starts
    #

    def list_all_mailbox_names(self):
        def filter_names(mboxes):
            return [m.mbox for m in mboxes]

        d = self.get_all_mailboxes()
        d.addCallback(filter_names)
        return d

    def get_all_mailboxes(self):
        d = self.adaptor.get_all_mboxes(self.store)
        return d

    def add_mailbox(self, name, creation_ts=None):
        return self.adaptor.atomic.run(self._add_mailbox,
                                       name,
                                       creation_ts=creation_ts)

    def _add_mailbox(self, name, creation_ts=None):

        if creation_ts is None:
            # by default, we pass an int value
            # taken from the current time
            # we make sure to take enough decimals to get a unique
            # mailbox-uidvalidity.
            creation_ts = int(time.time() * 10E2)

        def set_creation_ts(wrapper):
            wrapper.created = creation_ts
            d = wrapper.update(self.store)
            d.addCallback(lambda _: wrapper)
            return d

        def create_uuid(wrapper):
            if not wrapper.uuid:
                wrapper.uuid = str(uuid.uuid4())
                d = wrapper.update(self.store)
                d.addCallback(lambda _: wrapper)
                return d
            return wrapper

        def create_uid_table_cb(wrapper):
            d = self.mbox_indexer.create_table(wrapper.uuid)
            d.addCallback(lambda _: wrapper)
            return d

        d = self.adaptor.get_or_create_mbox(self.store, name)
        d.addCallback(set_creation_ts)
        d.addCallback(create_uuid)
        d.addCallback(create_uid_table_cb)
        return d

    def delete_mailbox(self, name):
        return self.adaptor.atomic.run(self._delete_mailbox, name)

    def _delete_mailbox(self, name):
        def delete_uid_table_cb(wrapper):
            d = self.mbox_indexer.delete_table(wrapper.uuid)
            d.addCallback(lambda _: wrapper)
            return d

        d = self.adaptor.get_or_create_mbox(self.store, name)
        d.addCallback(delete_uid_table_cb)
        d.addCallback(
            lambda wrapper: self.adaptor.delete_mbox(self.store, wrapper))
        return d

    def rename_mailbox(self, oldname, newname):
        return self.adaptor.atomic.run(self._rename_mailbox, oldname, newname)

    def _rename_mailbox(self, oldname, newname):
        def _rename_mbox(wrapper):
            wrapper.mbox = newname
            d = wrapper.update(self.store)
            d.addCallback(lambda result: wrapper)
            return d

        d = self.adaptor.get_or_create_mbox(self.store, oldname)
        d.addCallback(_rename_mbox)
        return d

    # Get Collections

    def get_collection_by_mailbox(self, name):
        """
        :rtype: deferred
        :return: a deferred that will fire with a MessageCollection
        """
        return self.adaptor.atomic.run(self._get_collection_by_mailbox, name)

    def _get_collection_by_mailbox(self, name):
        collection = self._collection_mapping[self.user_id].get(name, None)
        if collection:
            return defer.succeed(collection)

        # imap select will use this, passing the collection to SoledadMailbox
        def get_collection_for_mailbox(mbox_wrapper):
            collection = MessageCollection(self.adaptor, self.store,
                                           self.mbox_indexer, mbox_wrapper)
            self._collection_mapping[self.user_id][name] = collection
            return collection

        d = self.adaptor.get_or_create_mbox(self.store, name)
        d.addCallback(get_collection_for_mailbox)
        return d

    def get_collection_by_docs(self, docs):
        """
        :rtype: MessageCollection
        """
        # get a collection of docs by a list of doc_id
        # get.docs(...) --> it should be a generator
        raise NotImplementedError()

    def get_collection_by_tag(self, tag):
        """
        :rtype: MessageCollection
        """
        raise NotImplementedError()

    # Session handling

    def end_session(self):
        self._teardown_sync_hooks()
        self.session_ended = True