diff --git a/libervia/frontends/quick_frontend/quick_app.py b/libervia/frontends/quick_frontend/quick_app.py index 88cc9e72..418b9afc 100644 --- a/libervia/frontends/quick_frontend/quick_app.py +++ b/libervia/frontends/quick_frontend/quick_app.py @@ -349,7 +349,12 @@ class QuickApp: log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_)) 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): self.register_signal("connected") diff --git a/libervia/frontends/quick_frontend/quick_bridge.py b/libervia/frontends/quick_frontend/quick_bridge.py new file mode 100644 index 00000000..53999743 --- /dev/null +++ b/libervia/frontends/quick_frontend/quick_bridge.py @@ -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 . + +"""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()) diff --git a/libervia/frontends/quick_frontend/quick_profile_manager.py b/libervia/frontends/quick_frontend/quick_profile_manager.py index 880ee6eb..90631e23 100644 --- a/libervia/frontends/quick_frontend/quick_profile_manager.py +++ b/libervia/frontends/quick_frontend/quick_profile_manager.py @@ -233,9 +233,16 @@ class QuickProfileManager(object): def refill_profiles(self): """Rebuild the list of profiles""" - profiles = self.host.bridge.profiles_list_get() - profiles.sort() - self.set_profiles(profiles) + self.host.bridge.profiles_list_get( + callback=self._profiles_loaded, + 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 ##