134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Libervia CLI
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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
|
|
|
|
log.basicConfig(level=log.WARNING, format="[%(name)s] %(message)s")
|
|
|
|
USER_INTER_MSG = _("User interruption: good bye")
|
|
|
|
|
|
class QuitException(BaseException):
|
|
"""Quitting is requested
|
|
|
|
This is used to stop execution when host.quit() is called
|
|
"""
|
|
|
|
|
|
class LiberviaCLILoop:
|
|
"""Libervia CLI event loop.
|
|
|
|
This class must be created before bridge initialization, and "install_hooks" must be
|
|
called after.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize the loop.
|
|
|
|
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
|
|
|
|
# FIXME: Python 3.14 compatibility fix — see libervia/backend/core/launcher.py for details.
|
|
try:
|
|
asyncio.get_event_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
asyncioreactor.install()
|
|
from twisted.internet import reactor
|
|
|
|
self.loop = asyncio.get_event_loop()
|
|
self.reactor = reactor
|
|
self._hooks_installed = False
|
|
|
|
# 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_asyncio = True
|
|
|
|
def install_hooks(self, bridge_name):
|
|
"""Install signal handlers and event loop hooks.
|
|
|
|
This must be called after bridge initialization and before run().
|
|
|
|
@param bridge_name: Name of the bridge.
|
|
"""
|
|
if self._hooks_installed:
|
|
return
|
|
self._hooks_installed = True
|
|
|
|
import signal
|
|
|
|
signal.signal(signal.SIGINT, self._on_sigint)
|
|
|
|
def run(self, libervia_cli):
|
|
"""Run the event loop.
|
|
|
|
@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."
|
|
)
|
|
|
|
self._run_asyncio()
|
|
|
|
def _run_asyncio(self):
|
|
"""Run the dbus-based loop."""
|
|
loop = self.loop
|
|
loop.run_forever()
|
|
|
|
def quit(self, exit_code):
|
|
"""Stop the loop and exit.
|
|
|
|
@param exit_code: Exit code to return
|
|
"""
|
|
self._exit_code = exit_code
|
|
loop = asyncio.get_event_loop()
|
|
loop.stop()
|
|
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
|
|
"""
|
|
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 loop.
|
|
"""
|
|
print("\r" + USER_INTER_MSG)
|
|
self._exit_code = C.EXIT_USER_CANCELLED
|
|
self.quit(self._exit_code)
|