From 1cb931e83522746da668f9a8bb5943aca1882086 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 16 May 2013 04:26:00 +0900 Subject: use qtreactor so twisted is driven by qt main loop aboutToQuit signal is not raised anymore with the qt4reactor. So we are calling all cleanup callbacks from the quit function. --- changes/feature_use-qtreactor | 1 + pkg/requirements-dev.pip | 3 +- pkg/requirements.pip | 4 +- src/leap/app.py | 27 ++++++++++-- src/leap/gui/mainwindow.py | 76 ++++++++++++++++++---------------- src/leap/gui/twisted_main.py | 49 ++++++++++++++++++++++ src/leap/platform_init/initializers.py | 3 ++ src/leap/services/tx.py | 46 ++++++++++++++++++++ 8 files changed, 168 insertions(+), 41 deletions(-) create mode 100644 changes/feature_use-qtreactor create mode 100644 src/leap/gui/twisted_main.py create mode 100644 src/leap/services/tx.py diff --git a/changes/feature_use-qtreactor b/changes/feature_use-qtreactor new file mode 100644 index 00000000..154a99e5 --- /dev/null +++ b/changes/feature_use-qtreactor @@ -0,0 +1 @@ + o Use a qt4 reactor for twisted, for launching leap twisted services. diff --git a/pkg/requirements-dev.pip b/pkg/requirements-dev.pip index 23d50ceb..e241474a 100644 --- a/pkg/requirements-dev.pip +++ b/pkg/requirements-dev.pip @@ -11,5 +11,6 @@ # to install it. (do it after python setup.py develop and it # will only install this) --e git+git://github.com/leapcode/leap_pycommon.git@develop#egg=leap.common sphinx + +-e git+git://github.com/leapcode/leap_pycommon.git@develop#egg=leap.common diff --git a/pkg/requirements.pip b/pkg/requirements.pip index a225d0de..3c5bfad0 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -13,5 +13,7 @@ keyring python-dateutil psutil ipaddr +twisted +qt4reactor -leap.common>=0.2.1-dev +leap.common>=0.2.3-dev diff --git a/src/leap/app.py b/src/leap/app.py index bb8add0d..797cea8a 100644 --- a/src/leap/app.py +++ b/src/leap/app.py @@ -17,7 +17,6 @@ import logging import signal -import socket import sys from functools import partial @@ -28,14 +27,19 @@ from leap.common.events import server as event_server from leap.util import __version__ as VERSION from leap.util import leap_argparse from leap.gui import locale_rc +from leap.gui import twisted_main from leap.gui.mainwindow import MainWindow from leap.platform_init import IS_MAC from leap.platform_init.locks import we_are_the_one_and_only +from leap.services.tx import leap_services import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) +# pylint: avoid unused import +assert(locale_rc) + def sigint_handler(*args, **kwargs): """ @@ -48,9 +52,15 @@ def sigint_handler(*args, **kwargs): mainwindow.quit() +def install_qtreactor(logger): + import qt4reactor + qt4reactor.install() + logger.debug("Qt4 reactor installed") + + def main(): """ - Launches the main event loop + Starts the main event loop and launches the main window. """ event_server.ensure_server(event_server.SERVER_PORT) @@ -96,6 +106,9 @@ def main(): logger.info('Starting app') app = QtGui.QApplication(sys.argv) + # install the qt4reactor. + install_qtreactor(logger) + # To test: # $ LANG=es ./app.py locale = QtCore.QLocale.system().name() @@ -119,7 +132,10 @@ def main(): timer.start(500) timer.timeout.connect(lambda: None) - window = MainWindow(standalone, bypass_checks) + window = MainWindow( + lambda: twisted_main.quit(app), + standalone=standalone, + bypass_checks=bypass_checks) window.show() sigint_window = partial(sigint_handler, window, logger=logger) @@ -128,8 +144,11 @@ def main(): if IS_MAC: window.raise_() + tx_app = leap_services() + assert(tx_app) + # Run main loop - sys.exit(app.exec_()) + twisted_main.start(app) if __name__ == "__main__": main() diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index b3ab56d3..fdf84766 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -71,15 +71,22 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) - def __init__(self, standalone=False, bypass_checks=False): + def __init__(self, quit_callback, + standalone=False, bypass_checks=False): """ Constructor for the client main window + :param quit_callback: Function to be called when closing + the application. + :type quit_callback: callable + :param standalone: Set to true if the app should use configs - inside its pwd + inside its pwd :type standalone: bool + :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) @@ -89,6 +96,7 @@ class MainWindow(QtGui.QMainWindow): callback=self._new_updates_available) register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event) + self._quit_callback = quit_callback self._updates_content = "" @@ -173,27 +181,6 @@ class MainWindow(QtGui.QMainWindow): self._vpn.process_finished.connect( self._eip_finished) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._vpn.set_should_quit) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._vpn.wait) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._checker_thread.set_should_quit) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._checker_thread.wait) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._cleanup_pidfiles) - self.ui.chkRemember.stateChanged.connect( self._remember_state_changed) self.ui.chkRemember.setEnabled(keyring.get_keyring() is not None) @@ -447,12 +434,6 @@ class MainWindow(QtGui.QMainWindow): "More about LEAP" "") % (VERSION,)) - def quit(self): - self._really_quit = True - if self._wizard: - self._wizard.close() - self.close() - def changeEvent(self, e): """ Reimplements the changeEvent method to minimize to tray @@ -976,17 +957,42 @@ class MainWindow(QtGui.QMainWindow): def _cleanup_pidfiles(self): """ - SLOT - TRIGGERS: - self.aboutToQuit + Removes lockfiles on a clean shutdown. - Triggered on about to quit signal, removes lockfiles on a clean - shutdown + Triggered after aboutToQuit signal. """ if IS_WIN: lockfile = WindowsLock() lockfile.release_lock() + def _cleanup_and_quit(self): + """ + Call all the cleanup actions in a serialized way. + Should be called from the quit function. + """ + logger.debug('About to quit, doing cleanup...') + self._vpn.set_should_quit() + self._vpn.wait() + self._checker_thread.set_should_quit() + self._checker_thread.wait() + self._cleanup_pidfiles() + + def quit(self): + """ + Cleanup and tidely close the main window before quitting. + """ + self._cleanup_and_quit() + + self._really_quit = True + if self._wizard: + self._wizard.close() + self.close() + + if self._quit_callback: + self._quit_callback() + logger.debug('Bye.') + + if __name__ == "__main__": import signal diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py new file mode 100644 index 00000000..44f532a4 --- /dev/null +++ b/src/leap/gui/twisted_main.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# twisted_main.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Main functions for integration of twisted reactor +""" +import logging + +# Resist the temptation of putting the import reactor here, +# it will raise an "reactor already imported" error. + +logger = logging.getLogger(__name__) + + +def start(app): + """ + Start the mainloop. + + :param app: the main qt QApplication instance. + :type app: QtCore.QApplication + """ + from twisted.internet import reactor + logger.debug('starting twisted reactor') + reactor.run() + + +def quit(app): + """ + Stop the mainloop. + + :param app: the main qt QApplication instance. + :type app: QtCore.QApplication + """ + from twisted.internet import reactor + logger.debug('stopping twisted reactor') + reactor.stop() diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py index 91c7086b..2e8cbe95 100644 --- a/src/leap/platform_init/initializers.py +++ b/src/leap/platform_init/initializers.py @@ -28,6 +28,9 @@ from PySide import QtGui logger = logging.getLogger(__name__) +# NOTE we could use a deferToThread here, but should +# be aware of this bug: http://www.themacaque.com/?p=1067 + def init_platform(): """ diff --git a/src/leap/services/tx.py b/src/leap/services/tx.py new file mode 100644 index 00000000..ef08fcc6 --- /dev/null +++ b/src/leap/services/tx.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# twisted.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Twisted services launched by the client +""" +import logging + +from twisted.application.service import Application +from twisted.internet.task import LoopingCall + +logger = logging.getLogger(__name__) + + +def task(): + """ + stub periodic task, mainly for tests. + DELETE-ME when there's real meat here :) + """ + from datetime import datetime + logger.debug("hi there %s", datetime.now()) + + +def leap_services(): + """ + Check which twisted services are enabled and + register them. + """ + logger.debug('starting leap services') + application = Application("LEAP Client Local Services") + #lc = LoopingCall(task) + #lc.start(5) + return application -- cgit v1.2.3 From 212102f05bbc09b0b6cc6fa250eaafc8f80b6824 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 17 May 2013 00:45:06 +0900 Subject: catch reactor not running error --- src/leap/gui/twisted_main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py index 44f532a4..871af577 100644 --- a/src/leap/gui/twisted_main.py +++ b/src/leap/gui/twisted_main.py @@ -19,6 +19,8 @@ Main functions for integration of twisted reactor """ import logging +from twisted.internet import error + # Resist the temptation of putting the import reactor here, # it will raise an "reactor already imported" error. @@ -46,4 +48,7 @@ def quit(app): """ from twisted.internet import reactor logger.debug('stopping twisted reactor') - reactor.stop() + try: + reactor.stop() + except error.ReactorNotRunning: + logger.debug('reactor not running') -- cgit v1.2.3 From 319e1d55a2f8e9c521450f60c571f24a907553ee Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 17 May 2013 02:00:35 +0900 Subject: fix a segfault when stopping the reactor this particular way of invoking the run method in the reactor was giving trouble under some conditions. switching to runReturn makes it work. --- src/leap/gui/twisted_main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py index 871af577..c7add3ee 100644 --- a/src/leap/gui/twisted_main.py +++ b/src/leap/gui/twisted_main.py @@ -36,7 +36,13 @@ def start(app): """ from twisted.internet import reactor logger.debug('starting twisted reactor') - reactor.run() + + # this seems to be troublesome under some + # unidentified settings. + #reactor.run() + + reactor.runReturn() + app.exec_() def quit(app): -- cgit v1.2.3