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).
This commit is contained in:
Goffi 2026-04-27 15:45:09 +02:00
commit 8d42a09208
3 changed files with 57 additions and 32 deletions

View file

@ -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))

View file

@ -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:

View file

@ -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.