Add TaskThread, use to simplify WaitingDialog

This will be useful as a client thread for hardware wallets
This commit is contained in:
Neil Booth 2016-01-16 16:54:51 +09:00
parent d9a84875dc
commit c714acf739
2 changed files with 70 additions and 34 deletions

View File

@ -197,6 +197,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show() self.show()
self.raise_() self.raise_()
def on_error(self, exc_info):
traceback.print_exception(*exc_info)
self.show_error(str(exc_info[1]))
def on_network(self, event, *args): def on_network(self, event, *args):
if event == 'updated': if event == 'updated':
self.need_update.set() self.need_update.set()
@ -1265,12 +1269,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# call hook to see if plugin needs gui interaction # call hook to see if plugin needs gui interaction
run_hook('sign_tx', parent, tx) run_hook('sign_tx', parent, tx)
def sign_thread(): def on_signed(result):
self.wallet.sign_transaction(tx, password) callback(True)
return True def on_failed(exc_info):
self.on_error(exc_info)
callback(False)
WaitingDialog(parent, _('Signing transaction...'), sign_thread, task = partial(self.wallet.sign_transaction, tx, password)
callback) WaitingDialog(parent, _('Signing transaction...'), task,
on_signed, on_failed)
def broadcast_transaction(self, tx, tx_desc, parent): def broadcast_transaction(self, tx, tx_desc, parent):
@ -1309,7 +1316,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(msg, parent=parent) self.show_error(msg, parent=parent)
WaitingDialog(parent, _('Broadcasting transaction...'), WaitingDialog(parent, _('Broadcasting transaction...'),
broadcast_thread, broadcast_done) broadcast_thread, broadcast_done, self.on_error)
def prepare_for_payment_request(self): def prepare_for_payment_request(self):
self.tabs.setCurrentIndex(1) self.tabs.setCurrentIndex(1)

View File

@ -4,6 +4,8 @@ import traceback
import sys import sys
import threading import threading
import platform import platform
import Queue
from collections import namedtuple
from functools import partial from functools import partial
from electrum.i18n import _ from electrum.i18n import _
@ -211,39 +213,26 @@ class WindowModalDialog(QDialog, MessageBoxMixin):
if title: if title:
self.setWindowTitle(title) self.setWindowTitle(title)
class WaitingDialog(QThread, MessageBoxMixin):
class WaitingDialog(WindowModalDialog):
'''Shows a please wait dialog whilst runnning a task. It is not '''Shows a please wait dialog whilst runnning a task. It is not
necessary to maintain a reference to this dialog.''' necessary to maintain a reference to this dialog.'''
def __init__(self, parent, message, task, on_finished=None): def __init__(self, parent, message, task, on_success=None, on_error=None):
global dialogs assert parent
dialogs.append(self) # Prevent GC WindowModalDialog.__init__(self, parent, _("Please wait"))
QThread.__init__(self) vbox = QVBoxLayout(self)
self.task = task
self.on_finished = on_finished
self.dialog = WindowModalDialog(parent, _("Please wait"))
vbox = QVBoxLayout(self.dialog)
vbox.addWidget(QLabel(message)) vbox.addWidget(QLabel(message))
self.dialog.show() self.accepted.connect(self.on_accepted)
self.dialog.connect(self, SIGNAL("finished()"), self.finished) self.show()
self.start() self.thread = TaskThread(self)
self.thread.add(task, on_success, self.accept, on_error)
def run(self): def wait(self):
try: self.thread.wait()
self.result = self.task()
self.error = None def on_accepted(self):
except BaseException as e: self.thread.stop()
traceback.print_exc(file=sys.stdout)
self.error = str(e)
self.result = None
def finished(self):
global dialogs
dialogs.remove(self)
self.dialog.accept()
if self.error:
self.show_error(self.error, parent=self.dialog.parent())
if self.on_finished:
self.on_finished(self.result)
def line_dialog(parent, title, label, ok_label, default=None): def line_dialog(parent, title, label, ok_label, default=None):
dialog = WindowModalDialog(parent, title) dialog = WindowModalDialog(parent, title)
@ -548,6 +537,46 @@ class ButtonsTextEdit(QPlainTextEdit, ButtonsWidget):
return o return o
class TaskThread(QThread):
'''Thread that runs background tasks. Callbacks are guaranteed
to happen in the context of its parent.'''
Task = namedtuple("Task", "task cb_success cb_done cb_error")
doneSig = pyqtSignal(object, object, object)
def __init__(self, parent, on_error=None):
super(TaskThread, self).__init__(parent)
self.on_error = on_error
self.tasks = Queue.Queue()
self.doneSig.connect(self.on_done)
self.start()
def add(self, task, on_success=None, on_done=None, on_error=None):
on_error = on_error or self.on_error
self.tasks.put(TaskThread.Task(task, on_success, on_done, on_error))
def run(self):
while True:
task = self.tasks.get()
if not task:
break
try:
result = task.task()
self.doneSig.emit(result, task.cb_done, task.cb_success)
except BaseException:
self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)
def on_done(self, result, cb_done, cb):
# This runs in the parent's thread.
if cb_done:
cb_done()
if cb:
cb(result)
def stop(self):
self.tasks.put(None)
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication([]) app = QApplication([])
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done", _('OK'))) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done", _('OK')))