From 8d42a09208631b3735f84332a918e219eefa318d Mon Sep 17 00:00:00 2001 From: Goffi Date: Mon, 27 Apr 2026 15:45:09 +0200 Subject: [PATCH] core (disco), plugins identidy, XEP-0115: - Fix data types, and use quotes data type to avoid immediate evaluation on Python < 3.14 - Better error catching folling change in `get_infos` in `memory.disco`. - Some code modernisation (async coroutine). --- libervia/backend/memory/disco.py | 76 ++++++++++++------- .../backend/plugins/plugin_misc_identity.py | 7 ++ libervia/backend/plugins/plugin_xep_0115.py | 6 +- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/libervia/backend/memory/disco.py b/libervia/backend/memory/disco.py index 32e3b16d..103ba06c 100755 --- a/libervia/backend/memory/disco.py +++ b/libervia/backend/memory/disco.py @@ -21,6 +21,7 @@ from collections.abc import Iterable from typing import cast from twisted.internet.interfaces import IReactorCore +from libervia.backend import G from libervia.backend.core.i18n import _ from libervia.backend.core import exceptions from libervia.backend.core.log import getLogger @@ -37,6 +38,8 @@ from libervia.backend.memory import persistent from tx_xmpp import disco import time +from libervia.backend.tools.utils import ensure_deferred + log = getLogger(__name__) @@ -130,7 +133,10 @@ class Discovery: @param node: optional node to use for disco request @return: a Deferred which fire a boolean (True if feature is available) """ - disco_infos = await self.get_infos(client, jid_, node) + try: + disco_infos = await self.get_infos(client, jid_, node) + except (exceptions.ExternalRequestError, exceptions.TimeOutError): + return False return feature in disco_infos.features async def check_feature( @@ -235,10 +241,12 @@ class Discovery: f"Cached disco failure for {jid_.full()}, retry in " f"{DISCO_RETRY_DELAY - int(elapsed)} seconds" ) - return defer.fail(exceptions.ExternalRequestError( - f"Disco query failed for {jid_.full()}, retrying in " - f"{DISCO_RETRY_DELAY} seconds" - )) + return defer.fail( + exceptions.ExternalRequestError( + f"Disco query failed for {jid_.full()}, retrying in " + f"{DISCO_RETRY_DELAY} seconds" + ) + ) # Retry delay expired, remove from cache and retry log.debug(f"Disco retry delay expired for {jid_.full()}, retrying") del self._jid_disco_cache[jid_] @@ -286,14 +294,20 @@ class Discovery: disco_infos = self.hashes[cap_hash] return defer.succeed(disco_infos) - @defer.inlineCallbacks - def get_items(self, client, jid_=None, node="", use_cache=True): - """get disco items from jid_, cache them for our own server + async def get_items( + self, + client: SatXMPPEntity, + jid_: jid.JID | None = None, + node: str = "", + use_cache: bool = True, + ) -> disco.DiscoItems: + """get disco items from jid_, cache them for our own server. - @param jid_(jid.JID): jid of the target, or None for profile's server - @param node(unicode): optional node to use for disco request - @param use_cache(bool): if True, use cached data if available - @return: a Deferred which fire disco.DiscoItems + @param client: client session + @param jid_: jid of the target, or None for profile's server + @param node: optional node to use for disco request + @param use_cache: if True, use cached data if available + @return: disco.DiscoItems """ # FIXME: RSM is not managed here if jid_ is None: @@ -302,20 +316,20 @@ class Discovery: if jid_ == client.server_jid and not node: # we cache items only for our own server and if node is not set try: - items = self.host.memory.entity_data_get(client, jid_, ["DISCO_ITEMS"])[ - "DISCO_ITEMS" - ] + items = await self.host.memory.entity_data_get( + client, jid_, ["DISCO_ITEMS"] + )["DISCO_ITEMS"] log.debug("[%s] disco items are in cache" % jid_.full()) if not use_cache: # we ignore cache, so we pretend we haven't found it raise KeyError except (KeyError, exceptions.UnknownEntityError): - log.debug("Caching [%s] disco items" % jid_.full()) - items = yield client.disco.requestItems(jid_, nodeIdentifier=node) - self.host.memory.update_entity_data(client, jid_, "DISCO_ITEMS", items) + log.debug(f"Caching {jid_} disco items") + items = await client.disco.requestItems(jid_, nodeIdentifier=node) + G.host.memory.update_entity_data(client, jid_, "DISCO_ITEMS", items) else: try: - items = yield client.disco.requestItems(jid_, nodeIdentifier=node) + items = await client.disco.requestItems(jid_, nodeIdentifier=node) except StanzaError as e: log.warning( "Error while requesting items for {jid}: {reason}".format( @@ -379,7 +393,6 @@ class Discovery: for item in items: info_d = self.get_infos(client, item.entity) - info_d.addCallbacks( infos_cb, self._infos_eb, [item.entity], None, [item.entity] ) @@ -484,17 +497,22 @@ class Discovery: continue yield (item.entity.full(), item.nodeIdentifier or "", item.name or "") - @defer.inlineCallbacks - def _disco_items( - self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE - ): - """Discovery method for the bridge + @ensure_deferred + async def _disco_items( + self, + entity_jid_s: str, + node: str = "", + use_cache: bool = True, + profile_key: str = C.PROF_KEY_NONE, + ) -> list[tuple[str, str, str]]: + """Discovery method for the bridge. @param entity_jid_s: entity we want to discover - @param node(unicode): optional node to use - @param use_cache(bool): if True, use cached data if available - @return: list of tuples""" + @param node: optional node to use + @param use_cache: if True, use cached data if available + @return: list of (jid, node, name) tuples + """ client = self.host.get_client(profile_key) entity = jid.JID(entity_jid_s) - disco_items = yield self.get_items(client, entity, node, use_cache) + disco_items = await self.get_items(client, entity, node, use_cache) return list(self.items2tuples(disco_items)) diff --git a/libervia/backend/plugins/plugin_misc_identity.py b/libervia/backend/plugins/plugin_misc_identity.py index 0dcb0511..80825129 100644 --- a/libervia/backend/plugins/plugin_misc_identity.py +++ b/libervia/backend/plugins/plugin_misc_identity.py @@ -25,6 +25,7 @@ from typing import Any, Coroutine, Dict, List, Optional, Union, cast from twisted.internet import defer, reactor, threads from twisted.internet.interfaces import IReactorCore from twisted.words.protocols.jabber import jid +from twisted.words.protocols.jabber.error import StanzaError from libervia.backend.core import exceptions from libervia.backend.core.constants import Const as C @@ -750,6 +751,12 @@ class Identity: if entity is not None: try: disco_infos = await self.host.memory.disco.get_infos(client, entity) + except ( + exceptions.TimeOutError, + exceptions.ExternalRequestError, + StanzaError, + ) as e: + log.warning(f"Can't retrieve disco info for {entity}: {e}") except Exception as e: log.exception(f"Can't get disco infos for {entity}.") else: diff --git a/libervia/backend/plugins/plugin_xep_0115.py b/libervia/backend/plugins/plugin_xep_0115.py index d2d070fa..402b12a2 100644 --- a/libervia/backend/plugins/plugin_xep_0115.py +++ b/libervia/backend/plugins/plugin_xep_0115.py @@ -26,7 +26,7 @@ from twisted.internet import defer, error from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.xmlstream import XMPPHandler from twisted.words.xish import domish -from tx_xmpp import disco, itx_xmpp +from tx_xmpp import data_form, disco, itx_xmpp from zope.interface import implementer from libervia.backend import G @@ -98,7 +98,7 @@ class XEP_0115: log.info(_("Plugin XEP_0115 initialization")) host.trigger.add("Presence send", self._presence_trigger) - def get_handler(self, client: SatXMPPEntity) -> XEP_0115_handler: + def get_handler(self, client: SatXMPPEntity) -> "XEP_0115_handler": return XEP_0115_handler(self) async def _prepare_caps(self, client: SatXMPPEntity) -> None: @@ -426,7 +426,7 @@ class XEP_0115_handler(XMPPHandler): requestor: SatXMPPEntity, target: SatXMPPEntity, nodeIdentifier: str = "", - ) -> list[disco.DiscoFeature | disco.DiscoIdentity | disco.DiscoExtension]: + ) -> list[disco.DiscoFeature | disco.DiscoIdentity | data_form.Form]: """ Handle disco#info requests, including caps node queries.