#!/usr/bin/env python3 # Libervia: an XMPP client # Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """Adapte the legacy callback/errback bridge API to bridges""" import asyncio import inspect from typing import Any, Callable from twisted.internet import defer from twisted.python.failure import Failure from libervia.backend.core.log import getLogger log = getLogger(__name__) class QuickBridgeAdapter: """Adapt legacy callback/errback to bridges @param bridge: bridge instance """ def __init__(self, bridge: Any) -> None: self._bridge = bridge def __getattr__(self, name: str) -> Any: attr = getattr(self._bridge, name) if not callable(attr): return attr def wrapper(*args: Any, **kwargs: Any) -> Any: callback = kwargs.pop("callback", None) errback = kwargs.pop("errback", None) result = attr(*args, **kwargs) if inspect.iscoroutine(result): result = asyncio.ensure_future(result) if isinstance(result, asyncio.Future): # async bridges (e.g. AIOBridge) return asyncio futures result = defer.Deferred.fromFuture(result) if (callback is None and errback is None) or not hasattr( result, "addCallback" ): return result if callback is not None: result.addCallback(callback) if errback is not None: result.addErrback(errback) return result return wrapper def register_signal(self, name: str, handler: Callable, iface: str = "core") -> None: """Register a signal handler, adapting sync handlers to async bridges @param name: signal name @param handler: signal handler, sync or async @param iface: bridge interface ("core" or "plugin") """ if not inspect.iscoroutinefunction(handler): async def async_handler(*args: Any, _handler: Callable = handler) -> None: _handler(*args) handler = async_handler try: self._bridge.register_signal(name, handler, iface) except AttributeError: log.warning(f"Signal {name} is not available on the bridge") def bridge_connect(self, callback: Callable | None = None, errback: Callable | None = None) -> None: """Connect to the bridge @param callback: called when the bridge is connected @param errback: called with a twisted Failure on connection error """ async def _connect() -> None: try: await self._bridge.bridge_connect() except Exception as e: if errback is not None: errback(Failure(e)) else: if callback is not None: callback() try: asyncio.get_running_loop() except RuntimeError: # the event loop is not running yet (frontends usually call connect_bridge() # before starting their loop) try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.call_soon(asyncio.ensure_future, _connect()) else: asyncio.ensure_future(_connect())