118 lines
4 KiB
Python
118 lines
4 KiB
Python
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
"""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())
|