#!/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 . """tools dynamic import""" from importlib import import_module from types import ModuleType from typing import Any, Literal, LiteralString from libervia.backend.core import exceptions from libervia.backend.core.log import getLogger from libervia.backend.tools.config import ( BridgeSettings, DBusBridgeSettings, PbBridgeSettings, Settings, ) log = getLogger(__name__) module_path = "libervia.frontends.bridge" def import_bridge(name: str) -> ModuleType | None: """import bridge module @param name: name of the bridge to import (e.g.: dbus) @return: imported module or None if nothing is found """ try: bridge_module = import_module(module_path + "." + name) except ImportError: try: bridge_module = import_module(module_path + "." + name + "_bridge") except ImportError as e: log.warning(f"Can't import bridge {name!r}: {e}") bridge_module = None return bridge_module def _load_bridge(bridge_settings: BridgeSettings, bridge_type, verbose: bool): """Load requested bridge. @raise BridgeInitError: Bridge can't be initialised. """ bridge_name: str = bridge_settings.type bridge_module = import_bridge(bridge_name) if bridge_module is None: log.error(f"Can't find bridge module of name {bridge_name}") raise exceptions.BridgeInitError if verbose: log.info(f"using {bridge_name} bridge") # FIXME: temporary class selection, bridges need to be refactored to always use async # classes (and to not use a class starting with a lower case) try: bridge_cls = getattr(bridge_module, bridge_type) except AttributeError: bridge_cls = bridge_module.bridge try: return bridge_cls() except exceptions.BridgeInitError as e: raise e except Exception as e: log.exception(f"Can't load bridge {bridge_name}") raise exceptions.BridgeInitError from e def load_bridge( config: Settings, verbose: bool = True, bridge_type="AIOBridge" ) -> tuple[Any, BridgeSettings]: """Load and initialise the bridge. "auto" bridge is handled @param config: Libervia Settings @param is_backend: Set to True if calling from backend. @return: initialised bridge and bridge settings @raise BridgeInitError: Can't initialise this bridge. """ bridges = config.bridges # FIXME: We only handle first bridge for now. bridge_settings = next(iter(bridges)) bridge = None if bridge_settings.type == "auto": try: bridge_settings = DBusBridgeSettings() config.bridges = [bridge_settings] bridge = _load_bridge(bridge_settings, bridge_type, verbose=verbose) except exceptions.BridgeInitError as e: if verbose: log.warning(f"Can't load D-Bus bridge: {e}") log.info("Falling back to PB bridge.") bridge_settings = PbBridgeSettings() config.bridges = [bridge_settings] if bridge is None: bridge = _load_bridge(bridge_settings, bridge_type, verbose=verbose) return bridge, bridge_settings async def _load_and_init_bridge( bridge_settings: BridgeSettings, bridge_type: str, verbose: bool, is_backend: bool ): """Load and set requested bridge. @raise BridgeInitError: Bridge can't be initialised. @raise BridgeExceptionNoService: Bridge is initialised, but the backend can't be found. """ bridge = _load_bridge(bridge_settings, bridge_type, verbose) try: bridge_post_init = bridge.post_init except AttributeError: pass else: try: await bridge_post_init() except Exception as e: if verbose: log.warning(f"Could not initialize bridge: {e}") raise exceptions.BridgeInitError from e if not is_backend: # For frontends, we connect the bridge. try: await bridge.bridge_connect() except (exceptions.BridgeInitError, exceptions.BridgeExceptionNoService) as e: raise e except Exception as e: raise exceptions.BridgeInitError from e return bridge async def load_and_init_bridge( config: Settings, verbose: bool = True, bridge_type: Literal["AIOBridge", "TwistedBridge"] = "AIOBridge", is_backend=False, ) -> tuple[Any, BridgeSettings]: """Load and initialise the bridge. "auto" bridge is handled @param config: Libervia Settings @param verbose: Logs when a Bridge can't be initialised if True. @param bridge_type: Bridge class to use. @param is_backend: Set to True if calling from backend. @return: initialised bridge and bridge settings @raise BridgeInitError: Bridge can't be initialised. @raise BridgeExceptionNoService: Bridge is initialised, but the backend can't be found. """ if is_backend: global module_path module_path = "libervia.backend.bridge" bridges = config.bridges # FIXME: We only handle first bridge for now. bridge_settings = next(iter(bridges)) bridge = None if bridge_settings.type == "auto": try: bridge_settings = DBusBridgeSettings() config.bridges = [bridge_settings] bridge = await _load_and_init_bridge( bridge_settings, bridge_type, verbose, is_backend ) except exceptions.BridgeInitError as e: if verbose: log.warning(f"Can't load D-Bus bridge: {e}") log.info("Falling back to PB bridge.") bridge_settings = PbBridgeSettings() config.bridges = [bridge_settings] if bridge is None: bridge = await _load_and_init_bridge( bridge_settings, bridge_type, verbose, is_backend ) return bridge, bridge_settings