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.
This commit is contained in:
Goffi 2023-12-13 22:00:22 +01:00
commit e14dd9da8b
8 changed files with 83 additions and 10 deletions

View file

@ -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

View file

@ -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)

View file

@ -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"))

View file

@ -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

View file

@ -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,

View file

@ -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()

View file

@ -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()

View file

@ -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)