changeset 4193:730f542e4ad0

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.
author Goffi <goffi@goffi.org>
date Wed, 13 Dec 2023 22:00:22 +0100
parents 1d24ff583794
children 3dbaf179c50d
files libervia/backend/bridge/bridge_constructor/bridge_template.ini libervia/backend/bridge/dbus_bridge.py libervia/backend/core/main.py libervia/backend/memory/memory.py libervia/backend/tools/common/async_process.py libervia/cli/base.py libervia/frontends/bridge/dbus_bridge.py libervia/frontends/bridge/pb.py
diffstat 8 files changed, 83 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/libervia/backend/bridge/bridge_constructor/bridge_template.ini	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/backend/bridge/bridge_constructor/bridge_template.ini	Wed Dec 13 22:00:22 2023 +0100
@@ -237,6 +237,14 @@
 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
--- a/libervia/backend/bridge/dbus_bridge.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/backend/bridge/dbus_bridge.py	Wed Dec 13 22:00:22 2023 +0100
@@ -114,6 +114,7 @@
         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 @@
     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)
 
--- a/libervia/backend/core/main.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/backend/core/main.py	Wed Dec 13 22:00:22 2023 +0100
@@ -22,7 +22,7 @@
 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.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 @@
         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 @@
                 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 @@
             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"))
 
--- a/libervia/backend/memory/memory.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/backend/memory/memory.py	Wed Dec 13 22:00:22 2023 +0100
@@ -380,10 +380,10 @@
             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
--- a/libervia/backend/tools/common/async_process.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/backend/tools/common/async_process.py	Wed Dec 13 22:00:22 2023 +0100
@@ -133,7 +133,9 @@
         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,
--- a/libervia/cli/base.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/cli/base.py	Wed Dec 13 22:00:22 2023 +0100
@@ -713,7 +713,9 @@
                 )
                 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()
--- a/libervia/frontends/bridge/dbus_bridge.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/frontends/bridge/dbus_bridge.py	Wed Dec 13 22:00:22 2023 +0100
@@ -82,11 +82,7 @@
                                       '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 @@
             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 @@
         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()
--- a/libervia/frontends/bridge/pb.py	Tue Dec 12 12:17:15 2023 +0100
+++ b/libervia/frontends/bridge/pb.py	Wed Dec 13 22:00:22 2023 +0100
@@ -399,6 +399,15 @@
         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 @@
         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)