From e14dd9da8b3310cf54857a686d8b98ef4a39eec8 Mon Sep 17 00:00:00 2001 From: Goffi Date: Wed, 13 Dec 2023 22:00:22 +0100 Subject: [PATCH] core: add new `init_script_path` option: `init_script_path` option can be used in `[DEFAULTS]` to run a script at the end of backend initialisation. A new `init_pre_script` method is used to wait for backend to reach this stage (designed to be used mostly by CLI frontend), then the usual `ready_get` method is finished once the script is finished. --- .../bridge_constructor/bridge_template.ini | 8 +++++ libervia/backend/bridge/dbus_bridge.py | 4 +++ libervia/backend/core/main.py | 32 ++++++++++++++++++- libervia/backend/memory/memory.py | 4 +-- .../backend/tools/common/async_process.py | 4 ++- libervia/cli/base.py | 4 ++- libervia/frontends/bridge/dbus_bridge.py | 23 ++++++++++--- libervia/frontends/bridge/pb.py | 14 ++++++++ 8 files changed, 83 insertions(+), 10 deletions(-) diff --git a/libervia/backend/bridge/bridge_constructor/bridge_template.ini b/libervia/backend/bridge/bridge_constructor/bridge_template.ini index 221d1b38..67c21451 100644 --- a/libervia/backend/bridge/bridge_constructor/bridge_template.ini +++ b/libervia/backend/bridge/bridge_constructor/bridge_template.ini @@ -237,6 +237,14 @@ sig_in= sig_out= doc=Return when backend is initialised +[init_pre_script] +async= +type=method +category=core +sig_in= +sig_out= +doc=Return when backend is nearly initialised, just before init script is run, if any. + [version_get] type=method category=core diff --git a/libervia/backend/bridge/dbus_bridge.py b/libervia/backend/bridge/dbus_bridge.py index 99d672a6..2a7f2806 100644 --- a/libervia/backend/bridge/dbus_bridge.py +++ b/libervia/backend/bridge/dbus_bridge.py @@ -114,6 +114,7 @@ class DBusObject(objects.DBusObject): Method('image_convert', arguments='ssss', returns='s'), Method('image_generate_preview', arguments='ss', returns='s'), Method('image_resize', arguments='sii', returns='s'), + Method('init_pre_script', arguments='', returns=''), Method('is_connected', arguments='s', returns='b'), Method('main_resource_get', arguments='ss', returns='s'), Method('menu_help_get', arguments='ss', returns='s'), @@ -281,6 +282,9 @@ class DBusObject(objects.DBusObject): def dbus_image_resize(self, image_path, width, height): return self._callback("image_resize", image_path, width, height) + def dbus_init_pre_script(self, ): + return self._callback("init_pre_script", ) + def dbus_is_connected(self, profile_key="@DEFAULT@"): return self._callback("is_connected", profile_key) diff --git a/libervia/backend/core/main.py b/libervia/backend/core/main.py index a856544a..f12090f8 100644 --- a/libervia/backend/core/main.py +++ b/libervia/backend/core/main.py @@ -22,7 +22,7 @@ import uuid import hashlib import copy from pathlib import Path -from typing import Optional, List, Tuple, Dict +from typing import Optional, List, Tuple, Dict, cast from wokkel.data_form import Option from libervia import backend @@ -49,6 +49,7 @@ from libervia.backend.tools import image from libervia.backend.tools.common import dynamic_import from libervia.backend.tools.common import regex from libervia.backend.tools.common import data_format +from libervia.backend.tools.common import async_process from libervia.backend.stdui import ui_contact_list, ui_profile_manager import libervia.backend.plugins @@ -66,6 +67,10 @@ class LiberviaBackend(service.Service): self._menus = {} self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), # value: menu id + + # like initialised, but launched before init script is done, mainly useful for CLI + # frontend, so it can be used in init script, while other frontends are waiting. + self.init_pre_script = defer.Deferred() self.initialised = defer.Deferred() self.profiles = {} self.plugins = {} @@ -143,6 +148,7 @@ class LiberviaBackend(service.Service): reactor.callLater(0, self.stop) return + self.bridge.register_method("init_pre_script", lambda: self.init_pre_script) self.bridge.register_method("ready_get", lambda: self.initialised) self.bridge.register_method("version_get", lambda: self.full_version) self.bridge.register_method("features_get", self.features_get) @@ -239,6 +245,30 @@ class LiberviaBackend(service.Service): sys.exit(1) self._add_base_menus() + self.init_pre_script.callback(None) + + init_script = self.memory.config_get(None, "init_script_path") + if init_script is not None: + init_script = cast(str, init_script) + script_path = Path(init_script) + if not script_path.exists(): + log.error(f"Init script doesn't exist: {init_script}") + sys.exit(C.EXIT_BAD_ARG) + elif not script_path.is_file(): + log.error(f"Init script is not a file: {init_script}") + sys.exit(C.EXIT_BAD_ARG) + else: + log.info(f"Running init script {init_script!r}.") + try: + await async_process.run( + str(init_script), + verbose=True + ) + except RuntimeError as e: + log.error(f"Init script failed: {e}") + self.stopService() + sys.exit(C.EXIT_ERROR) + self.initialised.callback(None) log.info(_("Backend is ready")) diff --git a/libervia/backend/memory/memory.py b/libervia/backend/memory/memory.py index c34e0ae5..fd1bf2ca 100644 --- a/libervia/backend/memory/memory.py +++ b/libervia/backend/memory/memory.py @@ -380,10 +380,10 @@ class Memory: auth_d.addCallback(do_start_session) return auth_d - if self.host.initialised.called: + if self.host.init_pre_script.called: return defer.succeed(None).addCallback(backend_initialised) else: - return self.host.initialised.addCallback(backend_initialised) + return self.host.init_pre_script.addCallback(backend_initialised) def stop_session(self, profile): """Delete a profile session diff --git a/libervia/backend/tools/common/async_process.py b/libervia/backend/tools/common/async_process.py index 83a49450..e61fbe69 100644 --- a/libervia/backend/tools/common/async_process.py +++ b/libervia/backend/tools/common/async_process.py @@ -133,7 +133,9 @@ class CommandProtocol(protocol.ProcessProtocol): cmd_args = [command] + args if "env" not in kwargs: # we pass parent environment by default - kwargs["env"] = None + # FIXME: `None` doesn't seem to work, despite what documentation says, to be + # checked and reported upstream if confirmed. + kwargs["env"] = os.environ reactor.spawnProcess(prot, command, cmd_args, diff --git a/libervia/cli/base.py b/libervia/cli/base.py index d98a32ad..2c49b842 100644 --- a/libervia/cli/base.py +++ b/libervia/cli/base.py @@ -713,7 +713,9 @@ class LiberviaCli: ) self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False) return - await self.bridge.ready_get() + # we wait on init_pre_script instead of ready_get, so the CLI frontend can be used + # in init script. + await self.bridge.init_pre_script() self.version = await self.bridge.version_get() self._bridge_connected() self.import_plugins() diff --git a/libervia/frontends/bridge/dbus_bridge.py b/libervia/frontends/bridge/dbus_bridge.py index 5b400a0a..e1ab23d6 100644 --- a/libervia/frontends/bridge/dbus_bridge.py +++ b/libervia/frontends/bridge/dbus_bridge.py @@ -82,11 +82,7 @@ class bridge: 'org.freedesktop.DBus.Error.Spawn.ExecFailed'): errback(BridgeExceptionNoService()) elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported': - log.error(_( - 'D-Bus is not launched, please see documentation ' - '(doc/installation.rst, section "Launching D-Bus (on servers)") to ' - 'see instructions on how to launch it.' - )) + log.error(_("D-Bus is not launched, please see README to see instructions on how to launch it")) errback(BridgeInitError) else: errback(e) @@ -446,6 +442,15 @@ class bridge: error_handler = lambda err:errback(dbus_to_bridge_exception(err)) return str(self.db_core_iface.image_resize(image_path, width, height, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)) + def init_pre_script(self, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + return self.db_core_iface.init_pre_script(timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler) + def is_connected(self, profile_key="@DEFAULT@", callback=None, errback=None): if callback is None: error_handler = None @@ -1251,6 +1256,14 @@ class AIOBridge(bridge): self.db_core_iface.image_resize(image_path, width, height, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler) return fut + def init_pre_script(self): + loop = asyncio.get_running_loop() + fut = loop.create_future() + reply_handler = lambda ret=None: loop.call_soon_threadsafe(fut.set_result, ret) + error_handler = lambda err: loop.call_soon_threadsafe(fut.set_exception, dbus_to_bridge_exception(err)) + self.db_core_iface.init_pre_script(timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler) + return fut + def is_connected(self, profile_key="@DEFAULT@"): loop = asyncio.get_running_loop() fut = loop.create_future() diff --git a/libervia/frontends/bridge/pb.py b/libervia/frontends/bridge/pb.py index 2905cd63..06f08c51 100644 --- a/libervia/frontends/bridge/pb.py +++ b/libervia/frontends/bridge/pb.py @@ -399,6 +399,15 @@ class bridge(object): else: d.addErrback(self._errback, ori_errback=errback) + def init_pre_script(self, callback=None, errback=None): + d = self.root.callRemote("init_pre_script") + if callback is not None: + d.addCallback(lambda __: callback()) + if errback is None: + d.addErrback(self._generic_errback) + else: + d.addErrback(self._errback, ori_errback=errback) + def is_connected(self, profile_key="@DEFAULT@", callback=None, errback=None): d = self.root.callRemote("is_connected", profile_key) if callback is not None: @@ -955,6 +964,11 @@ class AIOBridge(bridge): d.addErrback(self._errback) return d.asFuture(asyncio.get_event_loop()) + def init_pre_script(self): + d = self.root.callRemote("init_pre_script") + d.addErrback(self._errback) + return d.asFuture(asyncio.get_event_loop()) + def is_connected(self, profile_key="@DEFAULT@"): d = self.root.callRemote("is_connected", profile_key) d.addErrback(self._errback)