From 7bf4c0aa6db8cbaa1befdb2841f722554a3a0731 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 21 Nov 2012 23:01:06 +0900 Subject: fixed ui freeze using queue for passing status between worker and parent --- src/leap/gui/firstrun/providerinfo.py | 119 +------------------ src/leap/gui/firstrun/providerselect.py | 200 ++++++++++++++++++++++++++++---- src/leap/gui/progress.py | 97 ++++++++++++---- src/leap/gui/threads.py | 8 +- 4 files changed, 265 insertions(+), 159 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index c5f39938..48763357 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -98,120 +98,8 @@ class ProviderInfoPage(ValidationPage): """ executes actual checks in a separate thread """ - finish = lambda: update_signal.emit("end_sentinel", 100) - - def pause_and_finish(): - # only for local debug - finish() - pause_for_user() - - wizard = self.wizard() - prevpage = "providerselection" - - full_domain = self.field('provider_domain') - - # we check if we have a port in the domain string. - domain, port = get_https_domain_and_port(full_domain) - _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain) - - netchecker = wizard.netchecker() - providercertchecker = wizard.providercertchecker() - eipconfigchecker = wizard.eipconfigchecker(domain=_domain) - - update_signal.emit("head_sentinel", 0) - pause_for_user() - - ######################## - # 1) try name resolution - ######################## - update_signal.emit("Checking that server is reachable", 20) - logger.debug('checking name resolution') - try: - netchecker.check_name_resolution( - domain) - - except baseexceptions.LeapException as exc: - logger.error(exc.message) - wizard.set_validation_error( - prevpage, exc.usermessage) - pause_and_finish() - return False - - ######################### - # 2) try https connection - ######################### - update_signal.emit("Checking secure connection to provider", 40) - logger.debug('checking https connection') - try: - providercertchecker.is_https_working( - "https://%s" % _domain, - verify=True) - - except eipexceptions.HttpsBadCertError as exc: - logger.debug('exception') - # XXX skipping for now... - ############################################## - # We had this validation logic - # in the provider selection page before - ############################################## - #if self.trustProviderCertCheckBox.isChecked(): - #pass - #else: - wizard.set_validation_error( - prevpage, exc.usermessage) - #fingerprint = certs.get_cert_fingerprint( - #domain=domain, sep=" ") - - # it's ok if we've trusted this fgprt before - #trustedcrts = wizard.trusted_certs - #if trustedcrts and fingerprint.replace(' ', '') in trustedcrts: - #pass - #else: - # let your user face panick :P - #self.add_cert_info(fingerprint) - #self.did_cert_check = True - #self.completeChanged.emit() - #return False - finish() - return False - - except baseexceptions.LeapException as exc: - wizard.set_validation_error( - prevpage, exc.usermessage) - finish() - return False - - ################################## - # 3) try download provider info... - ################################## - - update_signal.emit("Downloading provider info", 70) - try: - # XXX we already set _domain in the initialization - # so it should not be needed here. - eipconfigchecker.fetch_definition(domain=_domain) - wizard.set_providerconfig( - eipconfigchecker.defaultprovider.config) - except requests.exceptions.SSLError: - # XXX we should have catched this before. - # but cert checking is broken. - wizard.set_validation_error( - prevpage, - "Could not get info from provider.") - finish() - return False - except requests.exceptions.ConnectionError: - wizard.set_validation_error( - prevpage, - "Could not download provider info " - "(refused conn.).") - finish() - return False - # XXX catch more errors... - # We're done! self.set_done() - finish() def _do_validation(self): """ @@ -247,7 +135,12 @@ class ProviderInfoPage(ValidationPage): self.completeChanged.emit() def cleanupPage(self): - del self.wizard().providerconfig + wizard = self.wizard() + + # XXX makes sense now? + # this was created on previous... + if hasattr(wizard, 'providerconfig'): + del self.wizard().providerconfig if self.infoWidget: QtCore.QObjectCleanupHandler().add( diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 2786c494..08e09ee3 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -1,14 +1,17 @@ """ Select Provider Page, used in First Run Wizard """ +from functools import partial import logging +import requests + from PyQt4 import QtCore from PyQt4 import QtGui from leap.base import exceptions as baseexceptions #from leap.crypto import certs -#from leap.eip import exceptions as eipexceptions +from leap.eip import exceptions as eipexceptions from leap.gui.constants import APP_LOGO from leap.gui.progress import InlineValidationPage @@ -17,8 +20,24 @@ from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) +# XXX check newer version in progress... + + +def delay(obj, method_str): + """ + this is a hack to get responsiveness in the ui + """ + QtCore.QTimer().singleShot( + 10, + lambda: QtCore.QMetaObject.invokeMethod( + obj, method_str)) + class SelectProviderPage(InlineValidationPage): + + #disableCheckButton = QtCore.pyqtSignal() + launchChecks = QtCore.pyqtSignal() + def __init__(self, parent=None, providers=None): super(SelectProviderPage, self).__init__(parent) @@ -39,6 +58,11 @@ class SelectProviderPage(InlineValidationPage): self.setupSteps() self.setupUI() + #self.disableCheckButton.connect( + #self.onDisableCheckButton) + self.launchChecks.connect( + self.launch_checks) + def setupUI(self): """ initializes the UI @@ -149,21 +173,41 @@ class SelectProviderPage(InlineValidationPage): valFrame.setLayout(valframeLayout) self.valFrame = valFrame - # check domain - - def onCheckButtonClicked(self): + @QtCore.pyqtSlot() + def onDisableCheckButton(self): + print 'CHECK BUTTON DISABLED!!!' self.providerCheckButton.setDisabled(True) - self.valFrame.show() + + @QtCore.pyqtSlot() + def launch_checks(self): + # trying to delay this... + #timer = QtCore.QTimer() + #timer.singleShot(0, self.do_checks) self.do_checks() - def _do_checks(self, update_signal=None, failed_signal=None): + def onCheckButtonClicked(self): + #self.disableCheckButton.emit() + # XXX trying to get responsiveness. + # UI here is blocking, although I'm using + # threads and signals :( + QtCore.QMetaObject.invokeMethod( + self, "onDisableCheckButton") + + QtCore.QMetaObject.invokeMethod( + self, "showStepsFrame") + + delay(self, "launch_checks") + + print 'ON CHECK BUTTON --- DONE!' + print 'timer.....' + + def _do_checks(self): """ executes actual checks in a separate thread """ - finish = lambda: update_signal.emit("end_sentinel", 100) wizard = self.wizard() - prevpage = "providerselection" + curpage = "providerselection" full_domain = self.providerNameEdit.text() @@ -173,35 +217,143 @@ class SelectProviderPage(InlineValidationPage): netchecker = wizard.netchecker() - #providercertchecker = wizard.providercertchecker() - #eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + providercertchecker = wizard.providercertchecker() + eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + + def fail(): + self.is_done = False + return False - update_signal.emit("head_sentinel", 0) + yield(("head_sentinel", 0), lambda: None) ######################## # 1) try name resolution ######################## - update_signal.emit(self.tr("Can reach provider"), 20) logger.debug('checking name resolution') - try: - netchecker.check_name_resolution( - domain) - - except baseexceptions.LeapException as exc: - logger.error(exc.message) - wizard.set_validation_error( - prevpage, exc.usermessage) - failed_signal.emit() - self.is_done = False - return False + def namecheck(): + try: + netchecker.check_name_resolution( + domain) + + except baseexceptions.LeapException as exc: + logger.error(exc.message) + wizard.set_validation_error( + curpage, exc.usermessage) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + + # XXX catch more exceptions + + yield(("check name", 20), namecheck) + + ######################### + # 2) try https connection + ######################### + + logger.debug('checking https connection') + + def httpscheck(): + try: + providercertchecker.is_https_working( + "https://%s" % _domain, + verify=True) + + except eipexceptions.HttpsBadCertError as exc: + logger.debug('exception') + # XXX skipping for now... + ############################################## + # We had this validation logic + # in the provider selection page before + ############################################## + #if self.trustProviderCertCheckBox.isChecked(): + #pass + #else: + wizard.set_validation_error( + curpage, exc.usermessage) + #fingerprint = certs.get_cert_fingerprint( + #domain=domain, sep=" ") + + # it's ok if we've trusted this fgprt before + #trustedcrts = wizard.trusted_certs + #if trustedcrts and \ + # fingerprint.replace(' ', '') in trustedcrts: + #pass + #else: + # let your user face panick :P + #self.add_cert_info(fingerprint) + #self.did_cert_check = True + #self.completeChanged.emit() + #return False + return fail() + + except baseexceptions.LeapException as exc: + wizard.set_validation_error( + curpage, exc.usermessage) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + + yield(("https check", 40), httpscheck) + + ################################## + # 3) try download provider info... + ################################## + + def fetchinfo(): + try: + # XXX we already set _domain in the initialization + # so it should not be needed here. + eipconfigchecker.fetch_definition(domain=_domain) + wizard.set_providerconfig( + eipconfigchecker.defaultprovider.config) + except requests.exceptions.SSLError: + # XXX we should have catched this before. + # but cert checking is broken. + wizard.set_validation_error( + curpage, + self.tr( + "Could not get info from provider.")) + return fail() + except requests.exceptions.ConnectionError: + wizard.set_validation_error( + curpage, + self.tr( + "Could not download provider info " + "(refused conn.).")) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + yield(("fetch info", 80), fetchinfo) + + # done! self.is_done = True - finish() + yield(("end_sentinel", 100), lambda: None) def _inline_validation_ready(self): """ called after _do_checks has finished. """ + print 'VALIDATION READY ---------------' self.domain_checked = True if self.is_done: self.wizard().clean_validation_error(self.current_page) diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 33b0cb8c..9a5b352c 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -24,6 +24,24 @@ ICON_WAITING = ":/images/Emblem-question.png" logger = logging.getLogger(__name__) +# XXX import this from threads +def delay(obj, method_str=None, call_args=None): + """ + this is a hack to get responsiveness in the ui + """ + if callable(obj) and not method_str: + QtCore.QTimer().singleShot( + 50, + lambda: obj()) + return + + if method_str: + QtCore.QTimer().singleShot( + 50, + lambda: QtCore.QMetaObject.invokeMethod( + obj, method_str)) + + class ImgWidget(QtGui.QWidget): # XXX move to widgets @@ -168,6 +186,22 @@ class WithStepsMixIn(object): self.progress.setValue(progress) self.progress.update() + def processStepsQueue(self): + """ + consume steps queue + and pass messages + to the ui updater functions + """ + while self.queue.qsize(): + try: + status = self.queue.get(0) + if status == "failed": + self.set_failed_icon() + else: + self.onStepStatusChanged(*status) + except Queue.Empty: + pass + def setupSteps(self): self.steps = ProgressStepContainer() # steps table widget @@ -266,38 +300,59 @@ class WithStepsMixIn(object): """ -Resist the temptation to refactor the declaration of the signal -to the mixin. -PyQt and multiple inheritance do not mix well together. -You can only have one QObject base. -Therefore, we will use one base class for the intermediate pages +We will use one base class for the intermediate pages and another one for the in-page validations, both sharing the creation of the tablewidgets. +The logic of this split comes from where I was trying to solve +the ui update using signals, but now that it's working well with +queues I could join them again. """ +import Queue +from functools import partial -class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): - # signals - stepChanged = QtCore.pyqtSignal([str, int]) - stepFailed = QtCore.pyqtSignal() +class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): def __init__(self, parent=None): super(InlineValidationPage, self).__init__(parent) - self.connect_step_status() - self.connect_failstep_status() + + self.queue = Queue.Queue() + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.processStepsQueue) + self.timer.start(100) + self.threads = [] def do_checks(self): - """ - launches a thread to do the checks - """ - beupdate = self.stepChanged - befailed = self.stepFailed - self.checks = FunThread( - self._do_checks(update_signal=beupdate, failed_signal=befailed)) - self.checks.finished.connect(self._inline_validation_ready) - self.checks.begin() - #self.checks.wait() + + # yo dawg, I heard you like checks + # so I put a __do_checks in your do_checks + # for calling others' _do_checks + + def __do_checks(fun=None, queue=None): + + for checkcase in fun(): + checkmsg, checkfun = checkcase + + queue.put(checkmsg) + if checkfun() is False: + queue.put("failed") + break + + t = FunThread(fun=partial( + __do_checks, + fun=self._do_checks, + queue=self.queue)) + t.finished.connect(self._inline_validation_ready) + t.begin() + self.threads.append(t) + + # slot + + @QtCore.pyqtSlot() + def showStepsFrame(self): + self.valFrame.show() + self.update() class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): diff --git a/src/leap/gui/threads.py b/src/leap/gui/threads.py index 176c19b1..8aad8866 100644 --- a/src/leap/gui/threads.py +++ b/src/leap/gui/threads.py @@ -3,10 +3,16 @@ from PyQt4 import QtCore class FunThread(QtCore.QThread): - def __init__(self, fun, parent=None): + def __init__(self, fun=None, parent=None): + QtCore.QThread.__init__(self, parent) + self.exiting = False self.fun = fun + def __del__(self): + self.exiting = True + self.wait() + def run(self): if self.fun: self.fun() -- cgit v1.2.3