cli (base): Refactor loop handling:
The loop was chosen according to the bridge used, but this is not possible anymore before running it, because there is now an "auto" bridge which falls back from D-Bus to PB. To work around that, the loop has been refactored to be started before knowing which bridge is used, then the bridge-dependent hooks are installed later.
This commit is contained in:
parent
30619f5689
commit
1256e92cec
2 changed files with 135 additions and 102 deletions
|
|
@ -47,7 +47,7 @@ from libervia.backend.tools.common import data_format
|
|||
from libervia.backend.tools.common.ansi import ANSI as A
|
||||
import libervia.cli
|
||||
from libervia.cli.constants import Const as C
|
||||
from libervia.cli.loops import QuitException, get_libervia_cli_loop
|
||||
from libervia.cli.loops import QuitException, LiberviaCLILoop
|
||||
from libervia.frontends.bridge.bridge_frontend import BridgeException
|
||||
from libervia.frontends.tools import aio, misc
|
||||
from libervia.frontends.tools.jid import JID
|
||||
|
|
@ -828,6 +828,8 @@ class LiberviaCli:
|
|||
raise exceptions.InternalError(
|
||||
"Something went wrong while initialising bridge."
|
||||
) from e
|
||||
self.loop.install_hooks(self.bridge_settings.type)
|
||||
|
||||
# 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()
|
||||
|
|
@ -846,10 +848,10 @@ class LiberviaCli:
|
|||
return
|
||||
|
||||
def _run(self, args=None, namespace=None):
|
||||
LiberviaCLILoop = get_libervia_cli_loop(self.bridge_settings.type)
|
||||
self.loop = LiberviaCLILoop()
|
||||
try:
|
||||
self.loop.run(self, args, namespace)
|
||||
self.loop.loop.run_until_complete(self.main(args=args, namespace=namespace))
|
||||
self.loop.run(self)
|
||||
except SystemExit as e:
|
||||
# The loop is stopped, but we execute it one more time to call `a_quit` which
|
||||
# will call any cleaning method including async ones.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Libervia CLI
|
||||
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
|
||||
# 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
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
import sys
|
||||
import asyncio
|
||||
import logging as log
|
||||
from libervia.backend.core.exceptions import InternalError
|
||||
from libervia.backend.core.i18n import _
|
||||
from libervia.cli.constants import Const as C
|
||||
from libervia.frontends.tools import aio
|
||||
|
|
@ -35,112 +36,142 @@ class QuitException(BaseException):
|
|||
"""
|
||||
|
||||
|
||||
def get_libervia_cli_loop(bridge_name):
|
||||
if "dbus" in bridge_name:
|
||||
import signal
|
||||
class LiberviaCLILoop:
|
||||
"""Libervia CLI event loop.
|
||||
|
||||
class LiberviaCLILoop:
|
||||
This class must be created before bridge initialization, and "install_hooks" must be
|
||||
called after.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.loop = asyncio.get_event_loop()
|
||||
def __init__(self):
|
||||
"""Initialize the loop.
|
||||
|
||||
def run(self, libervia_cli, args, namespace):
|
||||
aio.install_glib_asyncio_iteration()
|
||||
signal.signal(signal.SIGINT, self._on_sigint)
|
||||
loop = self.loop
|
||||
loop.run_until_complete(libervia_cli.main(args=args, namespace=namespace))
|
||||
loop.run_forever()
|
||||
|
||||
def quit(self, exit_code):
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.stop()
|
||||
sys.exit(exit_code)
|
||||
|
||||
def call_later(self, delay, callback, *args):
|
||||
"""call a callback repeatedly
|
||||
|
||||
@param delay(int): delay between calls in s
|
||||
@param callback(callable): method to call
|
||||
if the callback return True, the call will continue
|
||||
else the calls will stop
|
||||
@param *args: args of the callbac
|
||||
"""
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.call_later(delay, callback, *args)
|
||||
|
||||
def _on_sigint(self, sig_number, stack_frame):
|
||||
"""Called on keyboard interruption
|
||||
|
||||
Print user interruption message, set exit code and stop reactor
|
||||
"""
|
||||
print("\r" + USER_INTER_MSG)
|
||||
self.quit(C.EXIT_USER_CANCELLED)
|
||||
|
||||
else:
|
||||
import signal
|
||||
The actual event loop backend (dbus/GLib or Twisted) is set up
|
||||
later via install_hooks() after bridge initialization.
|
||||
"""
|
||||
# we always install asyncioreactor because it must be done before any asyncio
|
||||
# loop is used. If we end up using dbus, this shouldn't cause trouble.
|
||||
from twisted.internet import asyncioreactor
|
||||
|
||||
asyncioreactor.install()
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet import reactor
|
||||
|
||||
class LiberviaCLILoop:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.reactor = reactor
|
||||
self._hooks_installed = False
|
||||
|
||||
def __init__(self):
|
||||
# exit code must be set when using quit, so if it's not set
|
||||
# something got wrong and we must report it
|
||||
self._exit_code = C.EXIT_INTERNAL_ERROR
|
||||
# exit code must be set when using quit, so if it's not set something got wrong
|
||||
# and we must report it
|
||||
self._exit_code = C.EXIT_INTERNAL_ERROR
|
||||
self._is_dbus = False
|
||||
|
||||
def run(self, libervia_cli, *args):
|
||||
self.libervia_cli = libervia_cli
|
||||
signal.signal(signal.SIGINT, self._on_sigint)
|
||||
defer.ensureDeferred(self._start(libervia_cli, *args))
|
||||
try:
|
||||
reactor.run(installSignalHandlers=False)
|
||||
except SystemExit as e:
|
||||
self._exit_code = e.code
|
||||
sys.exit(self._exit_code)
|
||||
def install_hooks(self, bridge_name):
|
||||
"""Install signal handlers and event loop hooks.
|
||||
|
||||
async def _start(self, libervia_cli, *args):
|
||||
fut = asyncio.ensure_future(libervia_cli.main(*args))
|
||||
try:
|
||||
await defer.Deferred.fromFuture(fut)
|
||||
except BaseException:
|
||||
import traceback
|
||||
This must be called after bridge initialization and before run().
|
||||
|
||||
traceback.print_exc()
|
||||
libervia_cli.quit(1)
|
||||
@param bridge_name: Name of the bridge.
|
||||
"""
|
||||
if self._hooks_installed:
|
||||
return
|
||||
self._hooks_installed = True
|
||||
|
||||
def quit(self, exit_code):
|
||||
self._exit_code = exit_code
|
||||
reactor.stop()
|
||||
import signal
|
||||
|
||||
def _timeout_cb(self, args, callback, delay):
|
||||
try:
|
||||
ret = callback(*args)
|
||||
# FIXME: temporary hack to avoid traceback when using XMLUI
|
||||
# to be removed once create_task is not used anymore in
|
||||
# xmlui_manager (i.e. once libervia.frontends.tools.xmlui fully supports
|
||||
# async syntax)
|
||||
except QuitException:
|
||||
return
|
||||
if ret:
|
||||
reactor.callLater(delay, self._timeout_cb, args, callback, delay)
|
||||
if "dbus" in bridge_name:
|
||||
self._is_dbus = True
|
||||
# Install GLib integration for dbus
|
||||
aio.install_glib_asyncio_iteration()
|
||||
elif bridge_name == "embedded":
|
||||
raise NotImplementedError
|
||||
else:
|
||||
self._is_dbus = False
|
||||
# Twisted reactor has already been installed in __init__
|
||||
|
||||
def call_later(self, delay, callback, *args):
|
||||
reactor.callLater(delay, self._timeout_cb, args, callback, delay)
|
||||
signal.signal(signal.SIGINT, self._on_sigint)
|
||||
|
||||
def _on_sigint(self, sig_number, stack_frame):
|
||||
"""Called on keyboard interruption
|
||||
def run(self, libervia_cli):
|
||||
"""Run the event loop.
|
||||
|
||||
Print user interruption message, set exit code and stop reactor
|
||||
"""
|
||||
print("\r" + USER_INTER_MSG)
|
||||
self._exit_code = C.EXIT_USER_CANCELLED
|
||||
reactor.callFromThread(reactor.stop)
|
||||
@param libervia_cli: The LiberviaCli instance
|
||||
"""
|
||||
if not self._hooks_installed:
|
||||
raise InternalError(
|
||||
"LiberviaCLILoop.run() called before install_hooks(). Ensure"
|
||||
" install_hooks(bridge_name) is called after bridge initialization."
|
||||
)
|
||||
|
||||
if bridge_name == "embedded":
|
||||
raise NotImplementedError
|
||||
# from sat.core import sat_main
|
||||
# sat = sat_main.SAT()
|
||||
if self._is_dbus:
|
||||
self._run_dbus()
|
||||
else:
|
||||
self._run_twisted(libervia_cli)
|
||||
|
||||
return LiberviaCLILoop
|
||||
def _run_dbus(self):
|
||||
"""Run the dbus-based loop."""
|
||||
loop = self.loop
|
||||
loop.run_forever()
|
||||
|
||||
def _run_twisted(self, libervia_cli):
|
||||
"""Run the Twisted-based loop."""
|
||||
self.libervia_cli = libervia_cli
|
||||
try:
|
||||
self.reactor.run(installSignalHandlers=False)
|
||||
except SystemExit as e:
|
||||
self._exit_code = e.code
|
||||
sys.exit(self._exit_code)
|
||||
|
||||
def quit(self, exit_code):
|
||||
"""Stop the loop and exit.
|
||||
|
||||
@param exit_code: Exit code to return
|
||||
"""
|
||||
self._exit_code = exit_code
|
||||
if self._is_dbus:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.stop()
|
||||
sys.exit(exit_code)
|
||||
else:
|
||||
# Only stop the reactor if it's running
|
||||
if self.reactor.running:
|
||||
self.reactor.stop()
|
||||
else:
|
||||
# Reactor not running (e.g., --help case), just exit
|
||||
sys.exit(exit_code)
|
||||
|
||||
def call_later(self, delay, callback, *args):
|
||||
"""Call a callback after a delay.
|
||||
|
||||
@param delay: Delay in seconds
|
||||
@param callback: Method to call
|
||||
@param args: Arguments to pass to callback
|
||||
"""
|
||||
if self._is_dbus:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.call_later(delay, callback, *args)
|
||||
else:
|
||||
self.reactor.callLater(delay, self._timeout_cb, args, callback, delay)
|
||||
|
||||
def _timeout_cb(self, args, callback, delay):
|
||||
"""Callback wrapper for Twisted-based delayed calls."""
|
||||
try:
|
||||
ret = callback(*args)
|
||||
# FIXME: temporary hack to avoid traceback when using XMLUI
|
||||
# to be removed once create_task is not used anymore in
|
||||
# xmlui_manager (i.e. once libervia.frontends.tools.xmlui fully supports
|
||||
# async syntax)
|
||||
except QuitException:
|
||||
return
|
||||
if ret:
|
||||
self.reactor.callLater(delay, self._timeout_cb, args, callback, delay)
|
||||
|
||||
def _on_sigint(self, sig_number, stack_frame):
|
||||
"""Called on keyboard interruption
|
||||
|
||||
Print user interruption message, set exit code and stop loop.
|
||||
"""
|
||||
print("\r" + USER_INTER_MSG)
|
||||
self._exit_code = C.EXIT_USER_CANCELLED
|
||||
if self._is_dbus:
|
||||
self.quit(self._exit_code)
|
||||
else:
|
||||
self.reactor.callFromThread(self.reactor.stop)
|
||||
|
|
|
|||
Loading…
Reference in a new issue