Browse Source

qt: implement server picker in server connect wizard

master
Sander van Grieken 2 years ago
parent
commit
2a2459c649
  1. 164
      electrum/gui/qt/network_dialog.py
  2. 35
      electrum/gui/qt/wizard/server_connect.py
  3. 37
      electrum/wizard.py

164
electrum/gui/qt/network_dialog.py

@ -95,9 +95,12 @@ class NodesListWidget(QTreeWidget):
DISCONNECTED_SERVER = 2 DISCONNECTED_SERVER = 2
TOPLEVEL = 3 TOPLEVEL = 3
def __init__(self, parent): followServer = pyqtSignal([object], arguments=['server'])
followChain = pyqtSignal([str], arguments=['chain_id'])
setServer = pyqtSignal([str], arguments=['server'])
def __init__(self):
QTreeWidget.__init__(self) QTreeWidget.__init__(self)
self.parent = parent # type: NetworkChoiceLayout
self.setHeaderLabels([_('Server'), _('Height')]) self.setHeaderLabels([_('Server'), _('Height')])
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.create_menu) self.customContextMenuRequested.connect(self.create_menu)
@ -110,16 +113,19 @@ class NodesListWidget(QTreeWidget):
menu = QMenu() menu = QMenu()
if item_type == self.ItemType.CONNECTED_SERVER: if item_type == self.ItemType.CONNECTED_SERVER:
server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr
menu.addAction(_("Use as server"), lambda: self.parent.follow_server(server)) def do_follow_server():
self.followServer.emit(server)
menu.addAction(_("Use as server"), do_follow_server)
elif item_type == self.ItemType.DISCONNECTED_SERVER: elif item_type == self.ItemType.DISCONNECTED_SERVER:
server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr
def func(): def do_set_server():
self.parent.server_e.setText(str(server)) self.setServer.emit(str(server))
self.parent.set_server() menu.addAction(_("Use as server"), do_set_server)
menu.addAction(_("Use as server"), func)
elif item_type == self.ItemType.CHAIN: elif item_type == self.ItemType.CHAIN:
chain_id = item.data(0, self.CHAIN_ID_ROLE) chain_id = item.data(0, self.CHAIN_ID_ROLE)
menu.addAction(_("Follow this branch"), lambda: self.parent.follow_branch(chain_id)) def do_follow_chain():
self.followChain.emit(chain_id)
menu.addAction(_("Follow this branch"), do_follow_chain)
else: else:
return return
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec_(self.viewport().mapToGlobal(position))
@ -136,9 +142,11 @@ class NodesListWidget(QTreeWidget):
pt.setX(50) pt.setX(50)
self.customContextMenuRequested.emit(pt) self.customContextMenuRequested.emit(pt)
def update(self, *, network: Network, servers: dict, use_tor: bool): def update(self, *, network: Network, servers: dict):
self.clear() self.clear()
use_tor = network.tor_proxy
# connected servers # connected servers
connected_servers_item = QTreeWidgetItem([_("Connected nodes"), '']) connected_servers_item = QTreeWidgetItem([_("Connected nodes"), ''])
connected_servers_item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.TOPLEVEL) connected_servers_item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.TOPLEVEL)
@ -146,7 +154,8 @@ class NodesListWidget(QTreeWidget):
n_chains = len(chains) n_chains = len(chains)
for chain_id, interfaces in chains.items(): for chain_id, interfaces in chains.items():
b = blockchain.blockchains.get(chain_id) b = blockchain.blockchains.get(chain_id)
if b is None: continue if b is None:
continue
name = b.get_name() name = b.get_name()
if n_chains > 1: if n_chains > 1:
x = QTreeWidgetItem([name + '@%d'%b.get_max_forkpoint(), '%d'%b.height()]) x = QTreeWidgetItem([name + '@%d'%b.get_max_forkpoint(), '%d'%b.height()])
@ -201,7 +210,9 @@ class NodesListWidget(QTreeWidget):
class NetworkChoiceLayout(object): class NetworkChoiceLayout(object):
# TODO consolidate to ProxyWidget+ServerWidget
# TODO TorDetector is unnecessary, Network tests socks5 peer and detects Tor
# TODO apply on editingFinished is not ideal, separate Apply button and on Close?
def __init__(self, network: Network, config: 'SimpleConfig', wizard=False): def __init__(self, network: Network, config: 'SimpleConfig', wizard=False):
self.network = network self.network = network
self.config = config self.config = config
@ -304,7 +315,14 @@ class NetworkChoiceLayout(object):
self.split_label = QLabel('') self.split_label = QLabel('')
grid.addWidget(self.split_label, 4, 0, 1, 3) grid.addWidget(self.split_label, 4, 0, 1, 3)
self.nodes_list_widget = NodesListWidget(self) self.nodes_list_widget = NodesListWidget()
self.nodes_list_widget.followServer.connect(self.follow_server)
self.nodes_list_widget.followChain.connect(self.follow_branch)
def do_set_server(server):
self.server_e.setText(server)
self.set_server()
self.nodes_list_widget.setServer.connect(do_set_server)
grid.addWidget(self.nodes_list_widget, 6, 0, 1, 5) grid.addWidget(self.nodes_list_widget, 6, 0, 1, 5)
vbox = QVBoxLayout() vbox = QVBoxLayout()
@ -361,8 +379,7 @@ class NetworkChoiceLayout(object):
msg = '' msg = ''
self.split_label.setText(msg) self.split_label.setText(msg)
self.nodes_list_widget.update(network=self.network, self.nodes_list_widget.update(network=self.network,
servers=self.network.get_servers(), servers=self.network.get_servers())
use_tor=self.tor_cb.isChecked())
self.enable_set_server() self.enable_set_server()
def fill_in_proxy_settings(self): def fill_in_proxy_settings(self):
@ -499,13 +516,11 @@ class ProxyWidget(QWidget):
grid = QGridLayout(self) grid = QGridLayout(self)
grid.setSpacing(8) grid.setSpacing(8)
# proxy setting # proxy setting.
self.proxy_cb = QCheckBox(_('Use proxy')) self.proxy_cb = QCheckBox(_('Use proxy'))
# self.proxy_cb.clicked.connect(self.check_disable_proxy)
# self.proxy_cb.clicked.connect(self.set_proxy)
self.proxy_mode = QComboBox() self.proxy_mode = QComboBox()
self.proxy_mode.addItems(['SOCKS4', 'SOCKS5']) self.proxy_mode.addItems(['SOCKS4', 'SOCKS5'])
self.proxy_mode.setCurrentIndex(1)
self.proxy_host = QLineEdit() self.proxy_host = QLineEdit()
self.proxy_host.setFixedWidth(fixed_width_hostname) self.proxy_host.setFixedWidth(fixed_width_hostname)
self.proxy_port = QLineEdit() self.proxy_port = QLineEdit()
@ -516,43 +531,37 @@ class ProxyWidget(QWidget):
self.proxy_password.setPlaceholderText(_("Password")) self.proxy_password.setPlaceholderText(_("Password"))
self.proxy_password.setFixedWidth(fixed_width_port) self.proxy_password.setFixedWidth(fixed_width_port)
# self.proxy_mode.currentIndexChanged.connect(self.set_proxy) grid.addWidget(self.proxy_cb, 0, 0, 1, 3)
# self.proxy_host.editingFinished.connect(self.set_proxy) grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 0, 4)
# self.proxy_port.editingFinished.connect(self.set_proxy) grid.addWidget(self.proxy_mode, 1, 1)
# self.proxy_user.editingFinished.connect(self.set_proxy) grid.addWidget(self.proxy_host, 1, 2)
# self.proxy_password.editingFinished.connect(self.set_proxy) grid.addWidget(self.proxy_port, 1, 3)
grid.addWidget(self.proxy_user, 2, 2)
# self.proxy_mode.currentIndexChanged.connect(self.proxy_settings_changed) grid.addWidget(self.proxy_password, 2, 3)
# self.proxy_host.textEdited.connect(self.proxy_settings_changed)
# self.proxy_port.textEdited.connect(self.proxy_settings_changed) def get_proxy_settings(self):
# self.proxy_user.textEdited.connect(self.proxy_settings_changed) return {
# self.proxy_password.textEdited.connect(self.proxy_settings_changed) 'enabled': self.proxy_cb.isChecked(),
'mode': ['socks4', 'socks5'][self.proxy_mode.currentIndex()],
self.tor_cb = QCheckBox(_("Use Tor Proxy")) 'host': self.proxy_host.text(),
self.tor_cb.setIcon(read_QIcon("tor_logo.png")) 'port': self.proxy_port.text(),
self.tor_cb.hide() 'user': self.proxy_user.text(),
# self.tor_cb.clicked.connect(self.use_tor_proxy) 'password': self.proxy_password.text()
}
grid.addWidget(self.tor_cb, 1, 0, 1, 3)
grid.addWidget(self.proxy_cb, 2, 0, 1, 3)
grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 2, 4) class ServerWidget(QWidget, QtEventListener):
grid.addWidget(self.proxy_mode, 4, 1) def __init__(self, network, parent=None):
grid.addWidget(self.proxy_host, 4, 2)
grid.addWidget(self.proxy_port, 4, 3)
grid.addWidget(self.proxy_user, 5, 2)
grid.addWidget(self.proxy_password, 5, 3)
grid.setRowStretch(7, 1)
class ServerWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.network = network
self.config = network.config
fixed_width_hostname = 24 * char_width_in_lineedit() fixed_width_hostname = 24 * char_width_in_lineedit()
fixed_width_port = 6 * char_width_in_lineedit() fixed_width_port = 6 * char_width_in_lineedit()
self.setLayout(QVBoxLayout())
grid = QGridLayout(self) grid = QGridLayout(self)
# self.setLayout(grid)
msg = ' '.join([ msg = ' '.join([
_("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."), _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."),
@ -564,9 +573,7 @@ class ServerWidget(QWidget):
grid.addWidget(HelpButton(msg), 0, 4) grid.addWidget(HelpButton(msg), 0, 4)
self.autoconnect_cb = QCheckBox(_('Select server automatically')) self.autoconnect_cb = QCheckBox(_('Select server automatically'))
# self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable())
# self.autoconnect_cb.clicked.connect(self.set_server)
# self.autoconnect_cb.clicked.connect(self.update)
msg = ' '.join([ msg = ' '.join([
_("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain."), _("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain."),
_("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging.") _("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging.")
@ -576,7 +583,6 @@ class ServerWidget(QWidget):
self.server_e = QLineEdit() self.server_e = QLineEdit()
self.server_e.setFixedWidth(fixed_width_hostname + fixed_width_port) self.server_e.setFixedWidth(fixed_width_hostname + fixed_width_port)
# self.server_e.editingFinished.connect(self.set_server)
msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.") msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.")
grid.addWidget(QLabel(_('Server') + ':'), 2, 0) grid.addWidget(QLabel(_('Server') + ':'), 2, 0)
grid.addWidget(self.server_e, 2, 1, 1, 3) grid.addWidget(self.server_e, 2, 1, 1, 3)
@ -591,16 +597,46 @@ class ServerWidget(QWidget):
self.split_label = QLabel('') self.split_label = QLabel('')
grid.addWidget(self.split_label, 4, 0, 1, 3) grid.addWidget(self.split_label, 4, 0, 1, 3)
self.nodes_list_widget = NodesListWidget(self) self.layout().addLayout(grid)
grid.addWidget(self.nodes_list_widget, 6, 0, 1, 5)
self.nodes_list_widget = NodesListWidget()
self.nodes_list_widget.followServer.connect(self.follow_server)
self.nodes_list_widget.followChain.connect(self.follow_branch)
def do_set_server(server):
self.server_e.setText(server)
self.set_server()
self.nodes_list_widget.setServer.connect(do_set_server)
self.layout().addWidget(self.nodes_list_widget)
self.nodes_list_widget.update(network=self.network,
servers=self.network.get_servers())
self.register_callbacks()
self.destroyed.connect(lambda: self.unregister_callbacks())
@qt_event_listener
def on_event_network_updated(self):
self.nodes_list_widget.update(network=self.network, servers=self.network.get_servers())
def follow_branch(self, chain_id):
self.network.run_from_another_thread(self.network.follow_chain_given_id(chain_id))
self.update()
# vbox = QVBoxLayout() def follow_server(self, server: ServerAddr):
# vbox.addWidget(tabs) self.server_e.setText(str(server))
# self.layout_ = vbox self.network.run_from_another_thread(self.network.follow_chain_given_server(server))
# # tor detector self.update()
# self.td = td = TorDetector()
# td.found_proxy.connect(self.suggest_proxy)
# td.start()
# self.fill_in_proxy_settings() def set_server(self):
# self.update() net_params = self.network.get_parameters()
try:
server = ServerAddr.from_str_with_inference(str(self.server_e.text()))
if not server:
raise Exception("failed to parse server")
except Exception:
return
net_params = net_params._replace(server=server,
auto_connect=self.autoconnect_cb.isChecked())
self.network.run_from_another_thread(self.network.set_parameters(net_params))

35
electrum/gui/qt/wizard/server_connect.py

@ -46,16 +46,7 @@ class WCAutoConnect(WizardComponent):
self._valid = True self._valid = True
def apply(self): def apply(self):
r = self.choice_w.selected_index self.wizard_data['autoconnect'] = (self.choice_w.selected_item[0] == 'autoconnect')
self.wizard_data['autoconnect'] = (r == 0)
# if r == 1:
# nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
# if self.exec_layout(nlayout.layout()):
# nlayout.accept()
# self.config.NETWORK_AUTO_CONNECT = network.auto_connect
# else:
# network.auto_connect = True
# self.config.NETWORK_AUTO_CONNECT = True
class WCProxyAsk(WizardComponent): class WCProxyAsk(WizardComponent):
@ -70,29 +61,31 @@ class WCProxyAsk(WizardComponent):
self._valid = True self._valid = True
def apply(self): def apply(self):
r = self.choice_w.selected_index self.wizard_data['want_proxy'] = (self.choice_w.selected_item[0] == 'yes')
self.wizard_data['want_proxy'] = (r == 0)
class WCProxyConfig(WizardComponent): class WCProxyConfig(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_("Proxy")) WizardComponent.__init__(self, parent, wizard, title=_("Proxy"))
pw = ProxyWidget(self) self.pw = ProxyWidget(self)
self.layout().addWidget(pw) self.pw.proxy_cb.setChecked(True)
self.pw.proxy_host.setText('localhost')
self.pw.proxy_port.setText('9050')
self.layout().addWidget(self.pw)
self.layout().addStretch(1) self.layout().addStretch(1)
self._valid = True
def apply(self): def apply(self):
# TODO self.wizard_data['proxy'] = self.pw.get_proxy_settings()
pass
class WCServerConfig(WizardComponent): class WCServerConfig(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_("Server")) WizardComponent.__init__(self, parent, wizard, title=_("Server"))
sw = ServerWidget(self) self.sw = ServerWidget(wizard._daemon.network, self)
self.layout().addWidget(sw) self.layout().addWidget(self.sw)
self.layout().addStretch(1) self._valid = True
def apply(self): def apply(self):
# TODO self.wizard_data['autoconnect'] = self.sw.autoconnect_cb.isChecked()
pass self.wizard_data['server'] = self.sw.server_e.text()

37
electrum/wizard.py

@ -4,6 +4,7 @@ import os
from typing import List, NamedTuple, Any, Dict, Optional, Tuple from typing import List, NamedTuple, Any, Dict, Optional, Tuple
from electrum.i18n import _ from electrum.i18n import _
from electrum.interface import ServerAddr
from electrum.keystore import hardware_keystore from electrum.keystore import hardware_keystore
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.plugin import run_hook from electrum.plugin import run_hook
@ -647,25 +648,57 @@ class ServerConnectWizard(AbstractWizard):
_logger = get_logger(__name__) _logger = get_logger(__name__)
def __init__(self, daemon): def __init__(self, daemon: 'Daemon'):
AbstractWizard.__init__(self) AbstractWizard.__init__(self)
self.navmap = { self.navmap = {
'autoconnect': { 'autoconnect': {
'next': 'server_config', 'next': 'server_config',
'accept': self.do_configure_autoconnect,
'last': lambda d: d['autoconnect'] 'last': lambda d: d['autoconnect']
}, },
'proxy_ask': { 'proxy_ask': {
'next': lambda d: 'proxy_config' if d['want_proxy'] else 'autoconnect' 'next': lambda d: 'proxy_config' if d['want_proxy'] else 'autoconnect'
}, },
'proxy_config': { 'proxy_config': {
'next': 'autoconnect' 'next': 'autoconnect',
'accept': self.do_configure_proxy
}, },
'server_config': { 'server_config': {
'accept': self.do_configure_server,
'last': True 'last': True
} }
} }
self._daemon = daemon self._daemon = daemon
def do_configure_proxy(self, wizard_data):
proxy_settings = wizard_data['proxy']
if not self._daemon.network:
self._logger.debug('not configuring proxy, electrum config wants offline mode')
return
self._logger.debug(f'configuring proxy: {proxy_settings!r}')
net_params = self._daemon.network.get_parameters()
if not proxy_settings['enabled']:
proxy_settings = None
net_params = net_params._replace(proxy=proxy_settings)
self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params))
def do_configure_server(self, wizard_data):
self._logger.debug(f'configuring server: {wizard_data!r}')
net_params = self._daemon.network.get_parameters()
try:
server = ServerAddr.from_str_with_inference(wizard_data['server'])
if not server:
raise Exception('failed to parse server %s' % wizard_data['server'])
except Exception:
return
net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect'])
self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params))
def do_configure_autoconnect(self, wizard_data):
self._logger.debug(f'configuring autoconnect: {wizard_data!r}')
if self._daemon.config.cv.NETWORK_AUTO_CONNECT.is_modifiable():
self._daemon.config.NETWORK_AUTO_CONNECT = wizard_data['autoconnect']
def start(self, initial_data=None): def start(self, initial_data=None):
if initial_data is None: if initial_data is None:
initial_data = {} initial_data = {}

Loading…
Cancel
Save