# HG changeset patch # User Goffi # Date 1569962950 -7200 # Node ID 3df611adb598dd022daf8b64c297daae1c377ec6 # Parent 964abd07dc03189f73b59a8406ea6c3d3d00ae0b jp: handle dbus bridge with asyncio: D-Bus bridge is now working again, using the new AsyncIO version. To make it work, the GLib loop is run in a separated thread. Loops have been moved to the `loops` module. diff -r 964abd07dc03 -r 3df611adb598 sat_frontends/jp/base.py --- a/sat_frontends/jp/base.py Tue Oct 01 22:49:10 2019 +0200 +++ b/sat_frontends/jp/base.py Tue Oct 01 22:49:10 2019 +0200 @@ -1,5 +1,4 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 # jp: a SAT command line tool # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) @@ -41,6 +40,7 @@ from sat.tools.common import date_utils from sat.core import exceptions import sat_frontends.jp +from sat_frontends.jp.loops import QuitException, getJPLoop from sat_frontends.jp.constants import Const as C from sat_frontends.tools import misc import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI @@ -50,109 +50,8 @@ # we get bridge name from conf and initialise the right class accordingly main_config = config.parseMainConf() bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus') -USER_INTER_MSG = _("User interruption: good bye") - - -class QuitException(BaseException): - """Quitting is requested - - This is used to stop execution when host.quit() is called - """ - - -# TODO: move loops handling in a separated module -if 'dbus' in bridge_name: - from gi.repository import GLib - - - class JPLoop(object): - - def __init__(self): - self.loop = GLib.MainLoop() - - def run(self): - self.loop.run() - - def quit(self, exit_code): - self.loop.quit() - sys.exit(exit_code) - - def call_later(self, delay, callback, *args): - """call a callback repeatedly - - @param delay(int): delay between calls in ms - @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 - """ - GLib.timeout_add(delay, callback, *args) - -else: - import signal - from twisted.internet import asyncioreactor - asyncioreactor.install() - from twisted.internet import reactor, defer - - class JPLoop(object): - - 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 +JPLoop = getJPLoop(bridge_name) - def run(self, jp, *args): - self.jp = jp - signal.signal(signal.SIGINT, self._on_sigint) - defer.ensureDeferred(self._start(jp, *args)) - try: - reactor.run(installSignalHandlers=False) - except SystemExit as e: - self._exit_code = e.code - sys.exit(self._exit_code) - - async def _start(self, jp, *args): - fut = asyncio.ensure_future(jp.main(*args)) - try: - await defer.Deferred.fromFuture(fut) - except BaseException: - import traceback - traceback.print_exc() - jp.quit(1) - - def quit(self, exit_code): - self._exit_code = exit_code - reactor.stop() - - 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 sat_frontends.tools.xmlui fully supports - # async syntax) - except QuitException: - return - if ret: - reactor.callLater(delay, self._timeout_cb, args, callback, delay) - - def call_later(self, delay, callback, *args): - delay = float(delay) / 1000 - 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 reactor - """ - print("\r" + USER_INTER_MSG) - self._exit_code = C.EXIT_USER_CANCELLED - reactor.callFromThread(reactor.stop) - - -if bridge_name == "embedded": - from sat.core import sat_main - sat = sat_main.SAT() try: import progressbar diff -r 964abd07dc03 -r 3df611adb598 sat_frontends/jp/loops.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sat_frontends/jp/loops.py Tue Oct 01 22:49:10 2019 +0200 @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +# jp: a SAT command line tool +# Copyright (C) 2009-2019 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 . + +import sys +import asyncio +import logging as log +from sat.core.i18n import _ +from sat_frontends.jp.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 + """ + + +def getJPLoop(bridge_name): + if 'dbus' in bridge_name: + import signal + import threading + from gi.repository import GLib + + class JPLoop: + + def run(self, jp, args, namespace): + signal.signal(signal.SIGINT, self._on_sigint) + self._glib_loop = GLib.MainLoop() + threading.Thread(target=self._glib_loop.run).start() + loop = asyncio.get_event_loop() + loop.run_until_complete(jp.main(args=args, namespace=namespace)) + loop.run_forever() + + def quit(self, exit_code): + loop = asyncio.get_event_loop() + loop.stop() + self._glib_loop.quit() + 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 + from twisted.internet import asyncioreactor + asyncioreactor.install() + from twisted.internet import reactor, defer + + class JPLoop: + + 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 + + def run(self, jp, *args): + self.jp = jp + signal.signal(signal.SIGINT, self._on_sigint) + defer.ensureDeferred(self._start(jp, *args)) + try: + reactor.run(installSignalHandlers=False) + except SystemExit as e: + self._exit_code = e.code + sys.exit(self._exit_code) + + async def _start(self, jp, *args): + fut = asyncio.ensure_future(jp.main(*args)) + try: + await defer.Deferred.fromFuture(fut) + except BaseException: + import traceback + traceback.print_exc() + jp.quit(1) + + def quit(self, exit_code): + self._exit_code = exit_code + reactor.stop() + + 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 sat_frontends.tools.xmlui fully supports + # async syntax) + except QuitException: + return + if ret: + reactor.callLater(delay, self._timeout_cb, args, callback, delay) + + def call_later(self, delay, callback, *args): + 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 reactor + """ + print("\r" + USER_INTER_MSG) + self._exit_code = C.EXIT_USER_CANCELLED + reactor.callFromThread(reactor.stop) + + + if bridge_name == "embedded": + raise NotImplementedError + # from sat.core import sat_main + # sat = sat_main.SAT() + + return JPLoop