quick_frontend: Q&D adapter to use legacy callback/errback with modern bridges

This commit is contained in:
Goffi 2026-11-04 17:45:14 +01:00
commit d3dc59b141
3 changed files with 134 additions and 4 deletions

View file

@ -349,7 +349,12 @@ class QuickApp:
log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_)) log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_))
def on_bridge_connected(self): def on_bridge_connected(self):
self.bridge.ready_get(self.on_backend_ready) d = self.bridge.ready_get()
d.addErrback(self._ready_get_eb)
d.addCallback(lambda __: self.on_backend_ready())
def _ready_get_eb(self, failure_):
log.error("Can't check backend readiness: {msg}".format(msg=failure_))
def _bridge_cb(self): def _bridge_cb(self):
self.register_signal("connected") self.register_signal("connected")

View file

@ -0,0 +1,118 @@
#!/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())

View file

@ -233,9 +233,16 @@ class QuickProfileManager(object):
def refill_profiles(self): def refill_profiles(self):
"""Rebuild the list of profiles""" """Rebuild the list of profiles"""
profiles = self.host.bridge.profiles_list_get() self.host.bridge.profiles_list_get(
profiles.sort() callback=self._profiles_loaded,
self.set_profiles(profiles) errback=self._profiles_list_eb,
)
def _profiles_loaded(self, profiles):
self.set_profiles(sorted(profiles))
def _profiles_list_eb(self, failure_):
log.error("Can't get profiles list: {msg}".format(msg=failure_))
## Method which must be implemented by frontends ## ## Method which must be implemented by frontends ##