File: service.py

package info (click to toggle)
python-aioxmpp 0.12.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,152 kB
  • sloc: python: 96,969; xml: 215; makefile: 155; sh: 72
file content (143 lines) | stat: -rw-r--r-- 5,109 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
########################################################################
# File name: service.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
import functools

import aioxmpp.callbacks
import aioxmpp.service


class ConversationService(aioxmpp.service.Service):
    """
    Central place where all :class:`.im.conversation.AbstractConversation`
    subclass instances are collected.

    It provides discoverability of all existing conversations (in no particular
    order) and signals on addition and removal of active conversations. This is
    useful for front ends to track conversations globally without needing to
    know about the specific conversation providers.

    .. signal:: on_conversation_added(conversation)

       A new conversation has been added.

       :param conversation: The conversation which was added.
       :type conversation: :class:`~.im.conversation.AbstractConversation`

       This signal is fired when a new conversation is added by a
       :term:`Conversation Implementation`.

       .. note::

          If you are looking for a "on_conversation_removed" event or similar,
          there is none. You should use the
          :meth:`.AbstractConversation.on_exit` event of the `conversation`.

    .. signal:: on_message(conversation,
                           *args, **kwargs)

        Emits whenever any active conversation emits its
        :meth:`~.im.Conversation.on_message` event. The arguments are forwarded
        1:1, with the :class:`~.im.AbstractConversation` instance pre-pended to
        the argument list.

    .. autoattribute:: conversations

    .. automethod:: get_conversation

    For :term:`Conversation Implementations <Conversation Implementation>`, the
    following methods are intended; they should not be used by applications.

    .. automethod:: _add_conversation

    """

    on_conversation_added = aioxmpp.callbacks.Signal()
    on_message = aioxmpp.callbacks.Signal()

    def __init__(self, client, **kwargs):
        super().__init__(client, **kwargs)
        self._conversation_meta = {}
        self._conversation_map = {}

    @property
    def conversations(self):
        """
        Return an iterable of conversations in which the local client is
        participating.
        """
        return self._conversation_meta.keys()

    def _remove_conversation(self, conv):
        del self._conversation_map[conv.jid]
        tokens, = self._conversation_meta.pop(conv)
        for signal, token in tokens:
            signal.disconnect(token)

    def _handle_conversation_exit(self, conv, *args, **kwargs):
        self._remove_conversation(conv)
        return False

    def _add_conversation(self, conversation):
        """
        Add the conversation and fire the :meth:`on_conversation_added` event.

        :param conversation: The conversation object to add.
        :type conversation: :class:`~.AbstractConversation`

        The conversation is added to the internal list of conversations which
        can be queried at :attr:`conversations`. The
        :meth:`on_conversation_added` event is fired.

        In addition, the :class:`ConversationService` subscribes to the
        :meth:`~.AbstractConversation.on_exit` event to remove the conversation
        from the list automatically. There is no need to remove a conversation
        from the list explicitly.
        """
        handler = functools.partial(
            self._handle_conversation_exit,
            conversation
        )
        tokens = []

        def linked_token(signal, handler):
            return signal, signal.connect(handler)

        tokens.append(linked_token(conversation.on_exit, handler))
        tokens.append(linked_token(conversation.on_failure, handler))
        tokens.append(linked_token(conversation.on_message, functools.partial(
            self.on_message,
            conversation,
        )))

        self._conversation_meta[conversation] = (
            tokens,
        )
        self._conversation_map[conversation.jid] = conversation
        self.on_conversation_added(conversation)

    def get_conversation(self, conversation_address):
        """
        Return the :class:`.im.AbstractConversation` for a given JID.

        :raises KeyError: if there is currently no matching conversation
        """
        return self._conversation_map[conversation_address]