qt gui: more resilient startup: catch more exceptions, better fallback
fixes https://github.com/spesmilo/electrum/issues/7447 Consider this trace for 4.2.0: ``` Traceback (most recent call last): File "electrum/gui/qt/__init__.py", line 332, in start_new_window File "electrum/gui/qt/__init__.py", line 363, in _start_wizard_to_select_or_create_wallet File "electrum/gui/qt/installwizard.py", line 302, in select_storage File "electrum/util.py", line 504, in get_new_wallet_name PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "electrum/gui/qt/__init__.py", line 426, in main File "electrum/gui/qt/__init__.py", line 307, in wrapper File "electrum/gui/qt/__init__.py", line 349, in start_new_window File "electrum/util.py", line 504, in get_new_wallet_name PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS' ``` Note that `get_new_wallet_name` (os.listdir) can raise OSError, and we were calling that on the main entrypoint codepath without exception-handling. We were also calling it in the fallback codepath without exception-handling. i.e. the GUI errored out on every startup for affected users, and without CLI usage it was not possible to recover.
This commit is contained in:
@@ -320,21 +320,33 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
return wrapper
|
||||
|
||||
@count_wizards_in_progress
|
||||
def start_new_window(self, path, uri, *, app_is_starting=False) -> Optional[ElectrumWindow]:
|
||||
def start_new_window(
|
||||
self,
|
||||
path,
|
||||
uri: Optional[str],
|
||||
*,
|
||||
app_is_starting: bool = False,
|
||||
force_wizard: bool = False,
|
||||
) -> Optional[ElectrumWindow]:
|
||||
'''Raises the window for the wallet if it is open. Otherwise
|
||||
opens the wallet and creates a new window for it'''
|
||||
wallet = None
|
||||
try:
|
||||
wallet = self.daemon.load_wallet(path, None)
|
||||
except Exception as e:
|
||||
self.logger.exception('')
|
||||
custom_message_box(icon=QMessageBox.Warning,
|
||||
parent=None,
|
||||
title=_('Error'),
|
||||
text=_('Cannot load wallet') + ' (1):\n' + repr(e))
|
||||
# if app is starting, still let wizard appear
|
||||
if not app_is_starting:
|
||||
return
|
||||
# Try to open with daemon first. If this succeeds, there won't be a wizard at all
|
||||
# (the wallet main window will appear directly).
|
||||
if not force_wizard:
|
||||
try:
|
||||
wallet = self.daemon.load_wallet(path, None)
|
||||
except Exception as e:
|
||||
self.logger.exception('')
|
||||
custom_message_box(icon=QMessageBox.Warning,
|
||||
parent=None,
|
||||
title=_('Error'),
|
||||
text=_('Cannot load wallet') + ' (1):\n' + repr(e))
|
||||
# if app is starting, still let wizard appear
|
||||
if not app_is_starting:
|
||||
return
|
||||
# Open a wizard window. This lets the user e.g. enter a password, or select
|
||||
# a different wallet.
|
||||
try:
|
||||
if not wallet:
|
||||
wallet = self._start_wizard_to_select_or_create_wallet(path)
|
||||
@@ -353,9 +365,19 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
||||
title=_('Error'),
|
||||
text=_('Cannot load wallet') + '(2) :\n' + repr(e))
|
||||
if app_is_starting:
|
||||
wallet_dir = os.path.dirname(path)
|
||||
path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
|
||||
self.start_new_window(path, uri)
|
||||
# If we raise in this context, there are no more fallbacks, we will shut down.
|
||||
# Worst case scenario, we might have gotten here without user interaction,
|
||||
# in which case, if we raise now without user interaction, the same sequence of
|
||||
# events is likely to repeat when the user restarts the process.
|
||||
# So we play it safe: clear path, clear uri, force a wizard to appear.
|
||||
try:
|
||||
wallet_dir = os.path.dirname(path)
|
||||
filename = get_new_wallet_name(wallet_dir)
|
||||
except OSError:
|
||||
path = self.config.get_fallback_wallet_path()
|
||||
else:
|
||||
path = os.path.join(wallet_dir, filename)
|
||||
self.start_new_window(path, uri=None, force_wizard=True)
|
||||
return
|
||||
if uri:
|
||||
window.pay_to_URI(uri)
|
||||
|
||||
@@ -202,6 +202,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
self.refresh_gui() # Need for QT on MacOSX. Lame.
|
||||
|
||||
def select_storage(self, path, get_wallet_from_daemon) -> Tuple[str, Optional[WalletStorage]]:
|
||||
if os.path.isdir(path):
|
||||
raise Exception("wallet path cannot point to a directory")
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
hbox = QHBoxLayout()
|
||||
@@ -297,9 +299,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
|
||||
button.clicked.connect(on_choose)
|
||||
button_create_new.clicked.connect(
|
||||
partial(
|
||||
name_e.setText,
|
||||
get_new_wallet_name(wallet_folder)))
|
||||
lambda: name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise
|
||||
name_e.textChanged.connect(on_filename)
|
||||
name_e.setText(os.path.basename(path))
|
||||
|
||||
|
||||
@@ -676,9 +676,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
except FileNotFoundError as e:
|
||||
self.show_error(str(e))
|
||||
return
|
||||
filename = get_new_wallet_name(wallet_folder)
|
||||
full_path = os.path.join(wallet_folder, filename)
|
||||
self.gui_object.start_new_window(full_path, None)
|
||||
try:
|
||||
filename = get_new_wallet_name(wallet_folder)
|
||||
except OSError as e:
|
||||
self.logger.exception("")
|
||||
self.show_error(repr(e))
|
||||
path = self.config.get_fallback_wallet_path()
|
||||
else:
|
||||
path = os.path.join(wallet_folder, filename)
|
||||
self.gui_object.start_new_window(path, uri=None, force_wizard=True)
|
||||
|
||||
def init_menubar(self):
|
||||
menubar = QMenuBar()
|
||||
|
||||
@@ -296,12 +296,7 @@ class SimpleConfig(Logger):
|
||||
if path and os.path.exists(path):
|
||||
return path
|
||||
|
||||
# default path
|
||||
util.assert_datadir_available(self.path)
|
||||
dirpath = os.path.join(self.path, "wallets")
|
||||
make_dir(dirpath, allow_symlink=False)
|
||||
|
||||
new_path = os.path.join(self.path, "wallets", "default_wallet")
|
||||
new_path = self.get_fallback_wallet_path()
|
||||
|
||||
# default path in pre 1.9 versions
|
||||
old_path = os.path.join(self.path, "electrum.dat")
|
||||
@@ -310,6 +305,13 @@ class SimpleConfig(Logger):
|
||||
|
||||
return new_path
|
||||
|
||||
def get_fallback_wallet_path(self):
|
||||
util.assert_datadir_available(self.path)
|
||||
dirpath = os.path.join(self.path, "wallets")
|
||||
make_dir(dirpath, allow_symlink=False)
|
||||
path = os.path.join(self.path, "wallets", "default_wallet")
|
||||
return path
|
||||
|
||||
def remove_from_recently_open(self, filename):
|
||||
recent = self.get('recently_open', [])
|
||||
if filename in recent:
|
||||
|
||||
@@ -498,6 +498,9 @@ def standardize_path(path):
|
||||
|
||||
|
||||
def get_new_wallet_name(wallet_folder: str) -> str:
|
||||
"""Returns a file basename for a new wallet to be used.
|
||||
Can raise OSError.
|
||||
"""
|
||||
i = 1
|
||||
while True:
|
||||
filename = "wallet_%d" % i
|
||||
|
||||
Reference in New Issue
Block a user