comparison libervia/backend/plugins/plugin_xep_0166/__init__.py @ 4288:f46891f2c9cb

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
author Goffi <goffi@goffi.org>
date Mon, 29 Jul 2024 03:30:58 +0200
parents 0d7bb4df2343
children e9971a4b0627
comparison
equal deleted inserted replaced
4287:ff88a807852d 4288:f46891f2c9cb
94 94
95 A_SESSION_INITIATE: Final = "session-initiate" 95 A_SESSION_INITIATE: Final = "session-initiate"
96 A_SESSION_ACCEPT: Final = "session-accept" 96 A_SESSION_ACCEPT: Final = "session-accept"
97 A_SESSION_TERMINATE: Final = "session-terminate" 97 A_SESSION_TERMINATE: Final = "session-terminate"
98 A_SESSION_INFO: Final = "session-info" 98 A_SESSION_INFO: Final = "session-info"
99 A_CONTENT_ADD: Final = "content-add"
100 A_CONTENT_MODIFY: Final = "content-modify"
101 A_CONTENT_REJECT: Final = "content-reject"
102 A_CONTENT_REMOVE: Final = "content-remove"
99 A_TRANSPORT_REPLACE: Final = "transport-replace" 103 A_TRANSPORT_REPLACE: Final = "transport-replace"
100 A_TRANSPORT_ACCEPT: Final = "transport-accept" 104 A_TRANSPORT_ACCEPT: Final = "transport-accept"
101 A_TRANSPORT_REJECT: Final = "transport-reject" 105 A_TRANSPORT_REJECT: Final = "transport-reject"
102 A_TRANSPORT_INFO: Final = "transport-info" 106 A_TRANSPORT_INFO: Final = "transport-info"
103 107
476 "The <iq> element provided doesn't have a <jingle> element" 480 "The <iq> element provided doesn't have a <jingle> element"
477 ) 481 )
478 else: 482 else:
479 iq_elt, jingle_elt = self._build_jingle_elt(client, session, action) 483 iq_elt, jingle_elt = self._build_jingle_elt(client, session, action)
480 # 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 484 # 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
481 content_data = session["contents"][content_name] 485 if action.startswith("content-"):
486 creator = session["role"]
487 transport_namespace = None
488 else:
489 content_data = session["contents"][content_name]
490 creator = content_data["creator"]
491 transport_namespace = content_data["transport"].namespace
492
482 content_elt = jingle_elt.addElement("content") 493 content_elt = jingle_elt.addElement("content")
483 content_elt["name"] = content_name 494 content_elt["name"] = content_name
484 content_elt["creator"] = content_data["creator"] 495 content_elt["creator"] = creator
485 496
486 if context_elt is not None: 497 if context_elt is not None:
487 if context_elt.parent is None: 498 if context_elt.parent is None:
488 content_elt.addChild(context_elt) 499 content_elt.addChild(context_elt)
489 elif action == XEP_0166.A_TRANSPORT_INFO: 500 elif action == XEP_0166.A_TRANSPORT_INFO:
490 context_elt = transport_elt = content_elt.addElement( 501 context_elt = transport_elt = content_elt.addElement(
491 "transport", content_data["transport"].namespace 502 "transport", transport_namespace
492 ) 503 )
493 else: 504 else:
494 raise exceptions.InternalError(f"unmanaged action {action}") 505 raise exceptions.InternalError(f"unmanaged action {action}")
495 506
496 return iq_elt, context_elt 507 return iq_elt, context_elt
511 try: 522 try:
512 return self._applications[namespace] 523 return self._applications[namespace]
513 except KeyError: 524 except KeyError:
514 raise exceptions.NotFound(f"No application registered for {namespace}") 525 raise exceptions.NotFound(f"No application registered for {namespace}")
515 526
516 def get_content_data(self, content: dict, content_idx: int) -> ContentData: 527 def get_content_data(
517 """ "Retrieve application and its argument from content""" 528 self, content: dict, content_idx: int | None = None
529 ) -> ContentData:
530 """Retrieve application and its argument from content"""
518 app_ns = content["app_ns"] 531 app_ns = content["app_ns"]
519 try: 532 try:
520 application = self.get_application(app_ns) 533 application = self.get_application(app_ns)
521 except exceptions.NotFound as e: 534 except exceptions.NotFound as e:
522 raise exceptions.InternalError(str(e)) 535 raise exceptions.InternalError(str(e))
524 app_kwargs = content.get("app_kwargs", {}) 537 app_kwargs = content.get("app_kwargs", {})
525 transport_data = content.get("transport_data", {}) 538 transport_data = content.get("transport_data", {})
526 try: 539 try:
527 content_name = content["name"] 540 content_name = content["name"]
528 except KeyError: 541 except KeyError:
542 if content_idx is None:
543 raise exceptions.InternalError(
544 '"content_idx" must be set if "content[\'name\']" is not set.'
545 )
529 content_name = content["name"] = str(content_idx) 546 content_name = content["name"] = str(content_idx)
530 return ContentData( 547 return ContentData(
531 application, app_args, app_kwargs, transport_data, content_name 548 application, app_args, app_kwargs, transport_data, content_name
532 ) 549 )
550
551 def get_transport(
552 self,
553 client: SatXMPPEntity,
554 content: dict,
555 content_data: ContentData,
556 ) -> TransportData:
557 """Find a suitable transport for given content"""
558 transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING)
559 for transport in self._type_transports[transport_type]:
560 if transport.handler.is_usable(client, content_data):
561 break
562 else:
563 raise exceptions.InternalError(
564 "No transport registered for {}".format(transport_type)
565 )
566 return transport
533 567
534 async def initiate( 568 async def initiate(
535 self, 569 self,
536 client: SatXMPPEntity, 570 client: SatXMPPEntity,
537 peer_jid: jid.JID, 571 peer_jid: jid.JID,
591 for content_idx, content in enumerate(contents): 625 for content_idx, content in enumerate(contents):
592 # we get the application plugin 626 # we get the application plugin
593 content_data = self.get_content_data(content, content_idx) 627 content_data = self.get_content_data(content, content_idx)
594 628
595 # and the transport plugin 629 # and the transport plugin
596 transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING) 630 transport = self.get_transport(client, content, content_data)
597 for transport in self._type_transports[transport_type]:
598 if transport.handler.is_usable(client, content_data):
599 break
600 else:
601 raise exceptions.InternalError(
602 "No transport registered for {}".format(transport_type)
603 )
604 631
605 # we build the session data for this content 632 # we build the session data for this content
606 application_data = {} 633 application_data = {}
607 transport_data = content_data.transport_data 634 transport_data = content_data.transport_data
608 session_content = { 635 session_content = {
799 await self.on_session_terminate(client, request, jingle_elt, session) 826 await self.on_session_terminate(client, request, jingle_elt, session)
800 elif action == XEP_0166.A_SESSION_ACCEPT: 827 elif action == XEP_0166.A_SESSION_ACCEPT:
801 await self.on_session_accept(client, request, jingle_elt, session) 828 await self.on_session_accept(client, request, jingle_elt, session)
802 elif action == XEP_0166.A_SESSION_INFO: 829 elif action == XEP_0166.A_SESSION_INFO:
803 await self.on_session_info(client, request, jingle_elt, session) 830 await self.on_session_info(client, request, jingle_elt, session)
831 elif action == XEP_0166.A_CONTENT_ADD:
832 await self.on_content_add(client, request, jingle_elt, session)
804 elif action == XEP_0166.A_TRANSPORT_INFO: 833 elif action == XEP_0166.A_TRANSPORT_INFO:
805 self.on_transport_info(client, request, jingle_elt, session) 834 self.on_transport_info(client, request, jingle_elt, session)
806 elif action == XEP_0166.A_TRANSPORT_REPLACE: 835 elif action == XEP_0166.A_TRANSPORT_REPLACE:
807 await self.on_transport_replace(client, request, jingle_elt, session) 836 await self.on_transport_replace(client, request, jingle_elt, session)
808 elif action == XEP_0166.A_TRANSPORT_ACCEPT: 837 elif action == XEP_0166.A_TRANSPORT_ACCEPT:
992 @param force_element: if elements is False, it is used as element parameter 1021 @param force_element: if elements is False, it is used as element parameter
993 else it is ignored 1022 else it is ignored
994 @return : list of launched methods results 1023 @return : list of launched methods results
995 @raise exceptions.NotFound: method is not implemented 1024 @raise exceptions.NotFound: method is not implemented
996 """ 1025 """
997 contents_dict = session["contents"] 1026 if action == self.A_CONTENT_ADD:
1027 contents_dict = session["contents_new"]
1028 else:
1029 contents_dict = session["contents"]
998 results = [] 1030 results = []
999 for content_name, content_data in contents_dict.items(): 1031 for content_name, content_data in contents_dict.items():
1000 for method_name, handler_key, default_cb, elt_name in ( 1032 for method_name, handler_key, default_cb, elt_name in (
1001 (app_method_name, "application", app_default_cb, "desc_elt"), 1033 (app_method_name, "application", app_default_cb, "desc_elt"),
1002 (transp_method_name, "transport", transp_default_cb, "transport_elt"), 1034 (transp_method_name, "transport", transp_default_cb, "transport_elt"),
1021 result = await utils.as_deferred( 1053 result = await utils.as_deferred(
1022 method, client, action, session, content_name, elt 1054 method, client, action, session, content_name, elt
1023 ) 1055 )
1024 results.append(result) 1056 results.append(result)
1025 1057
1058 if action == self.A_CONTENT_ADD:
1059 del session["contents_new"]
1060
1026 return results 1061 return results
1027 1062
1028 async def on_session_initiate( 1063 async def on_session_initiate(
1029 self, 1064 self,
1030 client: SatXMPPEntity, 1065 client: SatXMPPEntity,
1300 ) 1335 )
1301 except Exception: 1336 except Exception:
1302 log.exception("Error while managing session info") 1337 log.exception("Error while managing session info")
1303 else: 1338 else:
1304 client.send(xmlstream.toResponse(request, "result")) 1339 client.send(xmlstream.toResponse(request, "result"))
1340
1341 async def on_content_add(
1342 self,
1343 client: SatXMPPEntity,
1344 request: domish.Element,
1345 jingle_elt: domish.Element,
1346 session: Dict[str, Any],
1347 ) -> None:
1348 """Called on content-add action
1349
1350 The "jingle_request_confirmation" method of each application will be called
1351 (or self.jingle_request_confirmation_default if the former doesn't exist).
1352 The session is only accepted if all application are confirmed.
1353 The application must manage itself multiple contents scenari (e.g. audio/video).
1354 @param client: %(doc_client)s
1355 @param request(domish.Element): full request
1356 @param jingle_elt(domish.Element): <jingle> element
1357 @param session(dict): session data
1358 """
1359 return
1360 try:
1361 contents_dict = self._parse_elements(
1362 jingle_elt,
1363 {"id": session["id"], "contents": {}},
1364 request,
1365 client,
1366 True,
1367 XEP_0166.ROLE_INITIATOR,
1368 )
1369 except exceptions.CancelError:
1370 return
1371
1372 if not contents_dict:
1373 # there MUST be at least one content
1374 self.sendError(client, "bad-request", session["id"], request)
1375 return
1376
1377 session["contents_new"] = contents_dict
1378
1379 # at this point we can send the <iq/> result to confirm reception of the request
1380 client.send(xmlstream.toResponse(request, "result"))
1381
1382 assert "jingle_elt" not in session
1383 session["jingle_elt"] = jingle_elt
1384
1385 await self._call_plugins(client, XEP_0166.A_CONTENT_ADD, session, delete=False)
1305 1386
1306 async def on_transport_replace(self, client, request, jingle_elt, session): 1387 async def on_transport_replace(self, client, request, jingle_elt, session):
1307 """A transport change is requested 1388 """A transport change is requested
1308 1389
1309 The request is parsed, and jingle_handler is called on concerned transport plugin(s) 1390 The request is parsed, and jingle_handler is called on concerned transport plugin(s)