# HG changeset patch # User Goffi # Date 1722216658 -7200 # Node ID f46891f2c9cb14cca8eb1e86e760c5aa4a42ee39 # Parent ff88a807852d559be37bc5e01da81e9430b82539 plugin XEP-0166: handle `content-add` action + expose `get_transport`: - `content-add` is now handled at this plugin level (implementation needs to be done in apps and transports plugins). - `get_transport` is now exposed. rel 447 diff -r ff88a807852d -r f46891f2c9cb libervia/backend/plugins/plugin_xep_0166/__init__.py --- a/libervia/backend/plugins/plugin_xep_0166/__init__.py Mon Jul 29 03:30:53 2024 +0200 +++ b/libervia/backend/plugins/plugin_xep_0166/__init__.py Mon Jul 29 03:30:58 2024 +0200 @@ -96,6 +96,10 @@ A_SESSION_ACCEPT: Final = "session-accept" A_SESSION_TERMINATE: Final = "session-terminate" A_SESSION_INFO: Final = "session-info" + A_CONTENT_ADD: Final = "content-add" + A_CONTENT_MODIFY: Final = "content-modify" + A_CONTENT_REJECT: Final = "content-reject" + A_CONTENT_REMOVE: Final = "content-remove" A_TRANSPORT_REPLACE: Final = "transport-replace" A_TRANSPORT_ACCEPT: Final = "transport-accept" A_TRANSPORT_REJECT: Final = "transport-reject" @@ -478,17 +482,24 @@ else: iq_elt, jingle_elt = self._build_jingle_elt(client, session, action) # FIXME: XEP-0260 § 2.3 Ex 5 has an initiator attribute, but it should not according to XEP-0166 §7.1 table 1, must be checked - content_data = session["contents"][content_name] + if action.startswith("content-"): + creator = session["role"] + transport_namespace = None + else: + content_data = session["contents"][content_name] + creator = content_data["creator"] + transport_namespace = content_data["transport"].namespace + content_elt = jingle_elt.addElement("content") content_elt["name"] = content_name - content_elt["creator"] = content_data["creator"] + content_elt["creator"] = creator if context_elt is not None: if context_elt.parent is None: content_elt.addChild(context_elt) elif action == XEP_0166.A_TRANSPORT_INFO: context_elt = transport_elt = content_elt.addElement( - "transport", content_data["transport"].namespace + "transport", transport_namespace ) else: raise exceptions.InternalError(f"unmanaged action {action}") @@ -513,8 +524,10 @@ except KeyError: raise exceptions.NotFound(f"No application registered for {namespace}") - def get_content_data(self, content: dict, content_idx: int) -> ContentData: - """ "Retrieve application and its argument from content""" + def get_content_data( + self, content: dict, content_idx: int | None = None + ) -> ContentData: + """Retrieve application and its argument from content""" app_ns = content["app_ns"] try: application = self.get_application(app_ns) @@ -526,11 +539,32 @@ try: content_name = content["name"] except KeyError: + if content_idx is None: + raise exceptions.InternalError( + '"content_idx" must be set if "content[\'name\']" is not set.' + ) content_name = content["name"] = str(content_idx) return ContentData( application, app_args, app_kwargs, transport_data, content_name ) + def get_transport( + self, + client: SatXMPPEntity, + content: dict, + content_data: ContentData, + ) -> TransportData: + """Find a suitable transport for given content""" + transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING) + for transport in self._type_transports[transport_type]: + if transport.handler.is_usable(client, content_data): + break + else: + raise exceptions.InternalError( + "No transport registered for {}".format(transport_type) + ) + return transport + async def initiate( self, client: SatXMPPEntity, @@ -593,14 +627,7 @@ content_data = self.get_content_data(content, content_idx) # and the transport plugin - transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING) - for transport in self._type_transports[transport_type]: - if transport.handler.is_usable(client, content_data): - break - else: - raise exceptions.InternalError( - "No transport registered for {}".format(transport_type) - ) + transport = self.get_transport(client, content, content_data) # we build the session data for this content application_data = {} @@ -801,6 +828,8 @@ await self.on_session_accept(client, request, jingle_elt, session) elif action == XEP_0166.A_SESSION_INFO: await self.on_session_info(client, request, jingle_elt, session) + elif action == XEP_0166.A_CONTENT_ADD: + await self.on_content_add(client, request, jingle_elt, session) elif action == XEP_0166.A_TRANSPORT_INFO: self.on_transport_info(client, request, jingle_elt, session) elif action == XEP_0166.A_TRANSPORT_REPLACE: @@ -994,7 +1023,10 @@ @return : list of launched methods results @raise exceptions.NotFound: method is not implemented """ - contents_dict = session["contents"] + if action == self.A_CONTENT_ADD: + contents_dict = session["contents_new"] + else: + contents_dict = session["contents"] results = [] for content_name, content_data in contents_dict.items(): for method_name, handler_key, default_cb, elt_name in ( @@ -1023,6 +1055,9 @@ ) results.append(result) + if action == self.A_CONTENT_ADD: + del session["contents_new"] + return results async def on_session_initiate( @@ -1303,6 +1338,52 @@ else: client.send(xmlstream.toResponse(request, "result")) + async def on_content_add( + self, + client: SatXMPPEntity, + request: domish.Element, + jingle_elt: domish.Element, + session: Dict[str, Any], + ) -> None: + """Called on content-add action + + The "jingle_request_confirmation" method of each application will be called + (or self.jingle_request_confirmation_default if the former doesn't exist). + The session is only accepted if all application are confirmed. + The application must manage itself multiple contents scenari (e.g. audio/video). + @param client: %(doc_client)s + @param request(domish.Element): full request + @param jingle_elt(domish.Element): element + @param session(dict): session data + """ + return + try: + contents_dict = self._parse_elements( + jingle_elt, + {"id": session["id"], "contents": {}}, + request, + client, + True, + XEP_0166.ROLE_INITIATOR, + ) + except exceptions.CancelError: + return + + if not contents_dict: + # there MUST be at least one content + self.sendError(client, "bad-request", session["id"], request) + return + + session["contents_new"] = contents_dict + + # at this point we can send the result to confirm reception of the request + client.send(xmlstream.toResponse(request, "result")) + + assert "jingle_elt" not in session + session["jingle_elt"] = jingle_elt + + await self._call_plugins(client, XEP_0166.A_CONTENT_ADD, session, delete=False) + async def on_transport_replace(self, client, request, jingle_elt, session): """A transport change is requested