From 20d416a09f7f26d3146cadb97e5193ae30a52d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sun, 11 Nov 2018 11:39:51 +0100 Subject: [PATCH] presence: draft interface for Directed Presence management --- aioxmpp/presence/service.py | 216 ++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/aioxmpp/presence/service.py b/aioxmpp/presence/service.py index eba30443..4dac575e 100644 --- a/aioxmpp/presence/service.py +++ b/aioxmpp/presence/service.py @@ -236,6 +236,159 @@ def handle_presence(self, st): self.on_changed(st.from_, st) +class DirectedPresenceHandle: + """ + Represent a directed presence relationship with a peer. + + Directed Presence is specified in :rfc:`6121` section 4.6. Since the users + server is not responsible for distributing presence updates to peers to + which the client has sent directed presence, special handling is needed. + (The only presence automatically sent by a client’s server to a peer which + has received directed presence is the + :attr:`~aioxmpp.PresenceType.UNAVAILABLE` presence which is created when + the client disconnects.) + + .. note:: + + Directed presence relationships get + :meth:`unsubscribed ` immediately when the stream is + destroyed. This is because the peer has received + :attr:`~aioxmpp.PresenceType.UNAVAILABLE` presence from the client’s + server. + + .. autoattribute:: address + + .. autoattribute:: muted + + .. autoattribute:: presence_filter + + .. automethod:: set_muted + + .. automethod:: unsubscribe + + .. automethod:: send_presence + """ + + @property + def address(self) -> aioxmpp.JID: + """ + The address of the peer. This attribute is read-only. + + To change the address of a peer, + :meth:`aioxmpp.PresenceServer.rebind_directed_presence` can be used. + """ + + @property + def muted(self) -> bool: + """ + Flag to indicate whether the directed presence relationship is *muted*. + + If the relationship is **not** muted, presence updates made through the + :class:`PresenceServer` will be unicast to the peer entity of the + relationship. + + For a muted relationships, presence updates will *not* be automatically + sent to the peer. + + The *muted* behaviour is useful if presence updates need to be managed + by a service for some reason. + + This attribute is read-only. It must be modified through + :meth:`set_muted`. + """ + + @property + def presence_filter(self): + """ + Optional callback which is invoked on the presence stanza before it is + sent. + + This is called whenever a presence stanza is sent for this relationship + by the :class:`PresenceServer`. This is not invoked for presence stanzas + sent to the peer by other means (e.g. :meth:`aioxmpp.Client.send`). + + If the :attr:`presence_filter` is not :data:`None`, it is called with + the presence stanza as its only argument. It must either return the + presence stanza, or :data:`None`. If it returns :data:`None`, the + presence stanza is not sent. + + The callback operates on a copy of the presence stanza to prevent + modifications from leaking into other presence relationships; making a + copy inside the callback is not required or recommended. + """ + + @presence_filter.setter + def presence_filter(self, new_callback): + pass + + def set_muted(self, muted, *, send_update_now=True): + """ + Change the :attr:`muted` state of the relationship. + + (This is not a setter to the property due to the additional options + which are available when changing the :attr:`muted` state.) + + :param muted: The new muted state. + :type muted: :class:`bool` + :param send_update_now: Whether to send a presence update to the peer + immediately. + :type send_update_now: :class:`bool` + :raises RuntimeError: if the presence relationship has been destroyed + with :meth:`unsubscribe` + + If `muted` is equal to :attr:`muted`, this method does nothing. + + If `muted` is :data:`True`, the presence relationship will be muted. + `send_update_now` is ignored. + + If `muted` is :data:`False`, the presence relationship will be unmuted. + If `send_update_now` is :data:`True`, the current presence is sent to + the peer immediately. + """ + + def unsubscribe(self): + """ + Destroy the directed presence relationship. + + The presence relationship becomes useless afterwards. Any additional + calls to the methods will result in :class:`RuntimeError`. No additional + updates wil be sent to the peer automatically (except, of course, if + a new relationship is created). + + If the presence relationship is still active when the method is called, + :attr:`~aioxmpp.PresenceType.UNAVAILABLE` presence is sent to the peer + immediately. Otherwise, no stanza is sent. The stanza is passed through + the :attr:`presence_filter`. + + .. note:: + + Directed presence relationships get unsubscribed immediately when + the stream is destroyed. This is because the peer has received + :attr:`~aioxmpp.PresenceType.UNAVAILABLE` presence from the client’s + server. + + This operation is idempotent. + """ + + def send_presence(self, stanza: aioxmpp.Presence): + """ + Send a presence stanza to the peer. + + :param stanza: The stanza to send. + :type stanza: :class:`aioxmpp.Presence` + :raises RuntimeError: if the presence relationship has been destroyed + with :meth:`unsubscribe` + + The type of the presence `stanza` must be either + :attr:`aioxmpp.PresenceType.AVAILABLE` or + :attr:`aioxmpp.PresenceType.UNAVAILABLE`. + + The :meth:`presence_filter` is not invoked on this `stanza`; it is + assumed that the owner of the relationship takes care of setting the + `stanza` up in the way it is needed. + """ + + class PresenceServer(aioxmpp.service.Service): """ Manage the presence broadcast by the client. @@ -438,3 +591,66 @@ def resend_presence(self): if self.client.established: return self.client.enqueue(self.make_stanza()) + + def subscribe_peer_directed(self, + peer: aioxmpp.JID, + muted: bool = False): + """ + Create a directed presence relationship with a peer. + + :param peer: The address of the peer. This can be a full or bare JID. + :type peer: :class:`aioxmpp.JID` + :param muted: Flag to create the relationship in muted state. + :type muted: :class:`bool` + :rtype: :class:`DirectedPresenceHandle` + :return: The new directed presence handle. + + `peer` is the address of the peer which is going to receive directed + presence. For each bare JID, there can only exist either a single bare + JID directed presence relationship, or zero or more full JID directed + presence relationships. It is not possible to have a bare JID directed + presence relationship and a full JID directed presence relationship for + the same bare JID. + + If `muted` is :data:`False` (the default), the current presence is + unicast to `peer` when the relationship is created and the relationship + is created with :attr:`~.DirectedPresenceHandle.muted` set to + :data:`False`. + + If `muted` is :data:`True`, no presence is sent when the relationship is + created, and it is created with :attr:`~.DirectedPresenceHandle.muted` + set to :data:`True`. + + If the user of this method needs to set a + :attr:`~.DirectedPresenceHandle.presence_filter` on the relationship, + creating it with `muted` set to true is the only way to achieve this + before the initial directed presence to the peer is sent. + + The newly created handle is returned. + """ + + def rebind_directed_presence(self, + relationship: DirectedPresenceHandle, + new_peer: aioxmpp.JID): + """ + Modify the peer of an existing directed presence relationship. + + :param relationship: The relationship to operate on. + :type relationship: :class:`DirectedPresenceHandle` + :param new_peer: The new destination address of the relationship. + :type new_peer: :class:`aioxmpp.JID` + :raises RuntimeError: if the `relationship` has been destroyed with + :meth:`~DirectedPresenceHandle.unsubscribe` already. + :raises ValueError: if another relationship for `new_peer` exists and + is active + + This changes the peer :attr:`~.DirectedPresenceHandle.address` of the + `relationship` to `new_peer`. The conditions for peer addresses of + directed presence relationships as described in + :meth:`subscribe_peer_directed` are enforced in this operation. If they + are violated, :class:`ValueError` is raised. + + If the `relationship` has already been closed/destroyed using + :meth:`~DirectedPresenceHandle.unsubscribe`, :class:`RuntimeError` is + raised. + """