qt/qml: delay starting network until after first-start-network-setup
The qt, qml, and kivy GUIs have a first-start network-setup screen that allows the user customising the network settings before creating a wallet. Previously the daemon used to create the network and start it, before this screen, before the GUI even starts. If the user changed network settings, those would be set on the already running network, potentially including restarting the network. Now it becomes the responsibility of the GUI to start the network, allowing this first-start customisation to take place before starting the network at all. The qt and the qml GUIs are adapted to make use of this. Kivy, and the other prototype GUIs are not adapted and just start the network right away, as before.
This commit is contained in:
@@ -53,7 +53,7 @@ from .simple_config import SimpleConfig
|
||||
from .exchange_rate import FxThread
|
||||
from .logging import get_logger, Logger
|
||||
from . import GuiImportError
|
||||
from .plugin import run_hook
|
||||
from .plugin import run_hook, Plugins
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum import gui
|
||||
@@ -376,11 +376,19 @@ class WatchTowerServer(AuthenticatedServer):
|
||||
|
||||
class Daemon(Logger):
|
||||
|
||||
network: Optional[Network]
|
||||
gui_object: Optional['gui.BaseElectrumGui']
|
||||
network: Optional[Network] = None
|
||||
gui_object: Optional['gui.BaseElectrumGui'] = None
|
||||
watchtower: Optional['WatchTowerServer'] = None
|
||||
|
||||
@profiler
|
||||
def __init__(self, config: SimpleConfig, fd=None, *, listen_jsonrpc=True):
|
||||
def __init__(
|
||||
self,
|
||||
config: SimpleConfig,
|
||||
fd=None,
|
||||
*,
|
||||
listen_jsonrpc: bool = True,
|
||||
start_network: bool = True, # setting to False allows customising network settings before starting it
|
||||
):
|
||||
Logger.__init__(self)
|
||||
self.config = config
|
||||
self.listen_jsonrpc = listen_jsonrpc
|
||||
@@ -392,11 +400,9 @@ class Daemon(Logger):
|
||||
self.logger.warning("Ignoring parameter 'wallet_path' for daemon. "
|
||||
"Use the load_wallet command instead.")
|
||||
self.asyncio_loop = util.get_asyncio_loop()
|
||||
self.network = None
|
||||
if not config.get('offline'):
|
||||
self.network = Network(config, daemon=self)
|
||||
self.fx = FxThread(config=config)
|
||||
self.gui_object = None
|
||||
# path -> wallet; make sure path is standardized.
|
||||
self._wallets = {} # type: Dict[str, Abstract_Wallet]
|
||||
self._wallet_lock = threading.RLock()
|
||||
@@ -406,23 +412,14 @@ class Daemon(Logger):
|
||||
if listen_jsonrpc:
|
||||
self.commands_server = CommandsServer(self, fd)
|
||||
daemon_jobs.append(self.commands_server.run())
|
||||
# server-side watchtower
|
||||
self.watchtower = None
|
||||
watchtower_address = self.config.get_netaddress('watchtower_address')
|
||||
if not config.get('offline') and watchtower_address:
|
||||
self.watchtower = WatchTowerServer(self.network, watchtower_address)
|
||||
daemon_jobs.append(self.watchtower.run)
|
||||
if self.network:
|
||||
self.network.start(jobs=[self.fx.run])
|
||||
# prepare lightning functionality, also load channel db early
|
||||
if self.config.get('use_gossip', False):
|
||||
self.network.start_gossip()
|
||||
|
||||
self._stop_entered = False
|
||||
self._stopping_soon_or_errored = threading.Event()
|
||||
self._stopped_event = threading.Event()
|
||||
self.taskgroup = OldTaskGroup()
|
||||
asyncio.run_coroutine_threadsafe(self._run(jobs=daemon_jobs), self.asyncio_loop)
|
||||
if start_network and self.network:
|
||||
self.start_network()
|
||||
|
||||
@log_exceptions
|
||||
async def _run(self, jobs: Iterable = None):
|
||||
@@ -442,6 +439,20 @@ class Daemon(Logger):
|
||||
# not see the exception (especially if the GUI did not start yet).
|
||||
self._stopping_soon_or_errored.set()
|
||||
|
||||
def start_network(self):
|
||||
self.logger.info(f"starting network.")
|
||||
assert not self.config.get('offline')
|
||||
assert self.network
|
||||
# server-side watchtower
|
||||
if watchtower_address := self.config.get_netaddress('watchtower_address'):
|
||||
self.watchtower = WatchTowerServer(self.network, watchtower_address)
|
||||
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.watchtower.run), self.asyncio_loop)
|
||||
|
||||
self.network.start(jobs=[self.fx.run])
|
||||
# prepare lightning functionality, also load channel db early
|
||||
if self.config.get('use_gossip', False):
|
||||
self.network.start_gossip()
|
||||
|
||||
def with_wallet_lock(func):
|
||||
def func_wrapper(self: 'Daemon', *args, **kwargs):
|
||||
with self._wallet_lock:
|
||||
@@ -566,7 +577,7 @@ class Daemon(Logger):
|
||||
self.logger.info("stopped")
|
||||
self._stopped_event.set()
|
||||
|
||||
def run_gui(self, config, plugins):
|
||||
def run_gui(self, config: 'SimpleConfig', plugins: 'Plugins'):
|
||||
threading.current_thread().name = 'GUI'
|
||||
gui_name = config.get('gui', 'qt')
|
||||
if gui_name in ['lite', 'classic']:
|
||||
|
||||
@@ -64,6 +64,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
self.network = daemon.network
|
||||
|
||||
def main(self):
|
||||
self.daemon.start_network()
|
||||
from .main_window import ElectrumWindow
|
||||
w = ElectrumWindow(
|
||||
config=self.config,
|
||||
|
||||
@@ -409,6 +409,7 @@ ApplicationWindow
|
||||
Qt.callLater(Qt.quit)
|
||||
})
|
||||
dialog.accepted.connect(function() {
|
||||
Daemon.startNetwork()
|
||||
var newww = app.newWalletWizard.createObject(app)
|
||||
newww.walletCreated.connect(function() {
|
||||
Daemon.availableWallets.reload()
|
||||
@@ -419,6 +420,7 @@ ApplicationWindow
|
||||
})
|
||||
dialog.open()
|
||||
} else {
|
||||
Daemon.startNetwork()
|
||||
if (Daemon.availableWallets.rowCount() > 0) {
|
||||
Daemon.load_wallet()
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ from electrum.util import WalletFileException, standardize_path
|
||||
from electrum.wallet import Abstract_Wallet
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.lnchannel import ChannelState
|
||||
from electrum.daemon import Daemon
|
||||
|
||||
from .auth import AuthMixin, auth_protect
|
||||
from .qefx import QEFX
|
||||
@@ -135,7 +136,7 @@ class QEDaemon(AuthMixin, QObject):
|
||||
walletOpenError = pyqtSignal([str], arguments=["error"])
|
||||
walletDeleteError = pyqtSignal([str,str], arguments=['code', 'message'])
|
||||
|
||||
def __init__(self, daemon, parent=None):
|
||||
def __init__(self, daemon: 'Daemon', parent=None):
|
||||
super().__init__(parent)
|
||||
self.daemon = daemon
|
||||
self.qefx = QEFX(daemon.fx, daemon.config)
|
||||
@@ -336,3 +337,7 @@ class QEDaemon(AuthMixin, QObject):
|
||||
self._server_connect_wizard = QEServerConnectWizard(self)
|
||||
|
||||
return self._server_connect_wizard
|
||||
|
||||
@pyqtSlot()
|
||||
def startNetwork(self):
|
||||
self.daemon.start_network()
|
||||
|
||||
@@ -108,7 +108,7 @@ class QEServerListModel(QAbstractListModel, QtEventListener):
|
||||
server['address'] = i.server.to_friendly_name()
|
||||
server['height'] = i.tip
|
||||
|
||||
self._logger.debug(f'adding server: {repr(server)}')
|
||||
#self._logger.debug(f'adding server: {repr(server)}')
|
||||
servers.append(server)
|
||||
|
||||
# disconnected servers
|
||||
|
||||
@@ -428,12 +428,15 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
self.daemon.stop_wallet(window.wallet.storage.path)
|
||||
|
||||
def init_network(self):
|
||||
# Show network dialog if config does not exist
|
||||
"""Start the network, including showing a first-start network dialog if config does not exist."""
|
||||
if self.daemon.network:
|
||||
# first-start network-setup
|
||||
if self.config.get('auto_connect') is None:
|
||||
wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self)
|
||||
wizard.init_network(self.daemon.network)
|
||||
wizard.terminate()
|
||||
# start network
|
||||
self.daemon.start_network()
|
||||
|
||||
def main(self):
|
||||
# setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever
|
||||
@@ -443,7 +446,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
signal.signal(signal.SIGINT, lambda *args: self.app.quit())
|
||||
# hook for crash reporter
|
||||
Exception_Hook.maybe_setup(config=self.config)
|
||||
# first-start network-setup
|
||||
# start network, and maybe show first-start network-setup
|
||||
try:
|
||||
self.init_network()
|
||||
except UserCancelled:
|
||||
|
||||
@@ -177,7 +177,9 @@ class ElectrumGui(BaseElectrumGui, EventListener):
|
||||
|
||||
|
||||
def main(self):
|
||||
while self.done == 0: self.main_command()
|
||||
self.daemon.start_network()
|
||||
while self.done == 0:
|
||||
self.main_command()
|
||||
|
||||
def do_send(self):
|
||||
if not is_address(self.str_recipient):
|
||||
|
||||
@@ -517,6 +517,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
|
||||
pass
|
||||
|
||||
def main(self):
|
||||
self.daemon.start_network()
|
||||
tty.setraw(sys.stdin)
|
||||
try:
|
||||
while self.tab != -1:
|
||||
|
||||
@@ -141,7 +141,7 @@ class LNWatcher(Logger, EventListener):
|
||||
|
||||
LOGGING_SHORTCUT = 'W'
|
||||
|
||||
def __init__(self, adb, network: 'Network'):
|
||||
def __init__(self, adb: 'AddressSynchronizer', network: 'Network'):
|
||||
|
||||
Logger.__init__(self)
|
||||
self.adb = adb
|
||||
|
||||
@@ -331,6 +331,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
||||
|
||||
self._set_status('disconnected')
|
||||
self._has_ever_managed_to_connect_to_server = False
|
||||
self._was_started = False
|
||||
|
||||
# lightning network
|
||||
if self.config.get('run_watchtower', False):
|
||||
@@ -647,6 +648,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
||||
oneserver_changed = self.oneserver != net_params.oneserver
|
||||
default_server_changed = self.default_server != server
|
||||
self._init_parameters_from_config()
|
||||
if not self._was_started:
|
||||
return
|
||||
|
||||
async with self.restart_lock:
|
||||
if proxy_changed or oneserver_changed:
|
||||
@@ -1268,11 +1271,15 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
||||
Note: the jobs will *restart* every time the network restarts, e.g. on proxy
|
||||
setting changes.
|
||||
"""
|
||||
self._was_started = True
|
||||
self._jobs = jobs or []
|
||||
asyncio.run_coroutine_threadsafe(self._start(), self.asyncio_loop)
|
||||
|
||||
@log_exceptions
|
||||
async def stop(self, *, full_shutdown: bool = True):
|
||||
if not self._was_started:
|
||||
self.logger.info("not stopping network as it was never started")
|
||||
return
|
||||
self.logger.info("stopping network")
|
||||
# timeout: if full_shutdown, it is up to the caller to time us out,
|
||||
# otherwise if e.g. restarting due to proxy changes, we time out fast
|
||||
|
||||
@@ -434,7 +434,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
||||
fd = daemon.get_file_descriptor(config)
|
||||
if fd is not None:
|
||||
plugins = init_plugins(config, config.get('gui', 'qt'))
|
||||
d = daemon.Daemon(config, fd)
|
||||
d = daemon.Daemon(config, fd, start_network=False)
|
||||
try:
|
||||
d.run_gui(config, plugins)
|
||||
except BaseException as e:
|
||||
|
||||
Reference in New Issue
Block a user