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
|
return wrapper
|
||||||
|
|
||||||
@count_wizards_in_progress
|
@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
|
'''Raises the window for the wallet if it is open. Otherwise
|
||||||
opens the wallet and creates a new window for it'''
|
opens the wallet and creates a new window for it'''
|
||||||
wallet = None
|
wallet = None
|
||||||
try:
|
# Try to open with daemon first. If this succeeds, there won't be a wizard at all
|
||||||
wallet = self.daemon.load_wallet(path, None)
|
# (the wallet main window will appear directly).
|
||||||
except Exception as e:
|
if not force_wizard:
|
||||||
self.logger.exception('')
|
try:
|
||||||
custom_message_box(icon=QMessageBox.Warning,
|
wallet = self.daemon.load_wallet(path, None)
|
||||||
parent=None,
|
except Exception as e:
|
||||||
title=_('Error'),
|
self.logger.exception('')
|
||||||
text=_('Cannot load wallet') + ' (1):\n' + repr(e))
|
custom_message_box(icon=QMessageBox.Warning,
|
||||||
# if app is starting, still let wizard appear
|
parent=None,
|
||||||
if not app_is_starting:
|
title=_('Error'),
|
||||||
return
|
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:
|
try:
|
||||||
if not wallet:
|
if not wallet:
|
||||||
wallet = self._start_wizard_to_select_or_create_wallet(path)
|
wallet = self._start_wizard_to_select_or_create_wallet(path)
|
||||||
@@ -353,9 +365,19 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
title=_('Error'),
|
title=_('Error'),
|
||||||
text=_('Cannot load wallet') + '(2) :\n' + repr(e))
|
text=_('Cannot load wallet') + '(2) :\n' + repr(e))
|
||||||
if app_is_starting:
|
if app_is_starting:
|
||||||
wallet_dir = os.path.dirname(path)
|
# If we raise in this context, there are no more fallbacks, we will shut down.
|
||||||
path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
|
# Worst case scenario, we might have gotten here without user interaction,
|
||||||
self.start_new_window(path, uri)
|
# 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
|
return
|
||||||
if uri:
|
if uri:
|
||||||
window.pay_to_URI(uri)
|
window.pay_to_URI(uri)
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
self.refresh_gui() # Need for QT on MacOSX. Lame.
|
self.refresh_gui() # Need for QT on MacOSX. Lame.
|
||||||
|
|
||||||
def select_storage(self, path, get_wallet_from_daemon) -> Tuple[str, Optional[WalletStorage]]:
|
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()
|
vbox = QVBoxLayout()
|
||||||
hbox = QHBoxLayout()
|
hbox = QHBoxLayout()
|
||||||
@@ -297,9 +299,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
|
|
||||||
button.clicked.connect(on_choose)
|
button.clicked.connect(on_choose)
|
||||||
button_create_new.clicked.connect(
|
button_create_new.clicked.connect(
|
||||||
partial(
|
lambda: name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise
|
||||||
name_e.setText,
|
|
||||||
get_new_wallet_name(wallet_folder)))
|
|
||||||
name_e.textChanged.connect(on_filename)
|
name_e.textChanged.connect(on_filename)
|
||||||
name_e.setText(os.path.basename(path))
|
name_e.setText(os.path.basename(path))
|
||||||
|
|
||||||
|
|||||||
@@ -676,9 +676,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
self.show_error(str(e))
|
self.show_error(str(e))
|
||||||
return
|
return
|
||||||
filename = get_new_wallet_name(wallet_folder)
|
try:
|
||||||
full_path = os.path.join(wallet_folder, filename)
|
filename = get_new_wallet_name(wallet_folder)
|
||||||
self.gui_object.start_new_window(full_path, None)
|
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):
|
def init_menubar(self):
|
||||||
menubar = QMenuBar()
|
menubar = QMenuBar()
|
||||||
|
|||||||
@@ -296,12 +296,7 @@ class SimpleConfig(Logger):
|
|||||||
if path and os.path.exists(path):
|
if path and os.path.exists(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# default path
|
new_path = self.get_fallback_wallet_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")
|
|
||||||
|
|
||||||
# default path in pre 1.9 versions
|
# default path in pre 1.9 versions
|
||||||
old_path = os.path.join(self.path, "electrum.dat")
|
old_path = os.path.join(self.path, "electrum.dat")
|
||||||
@@ -310,6 +305,13 @@ class SimpleConfig(Logger):
|
|||||||
|
|
||||||
return new_path
|
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):
|
def remove_from_recently_open(self, filename):
|
||||||
recent = self.get('recently_open', [])
|
recent = self.get('recently_open', [])
|
||||||
if filename in recent:
|
if filename in recent:
|
||||||
|
|||||||
@@ -498,6 +498,9 @@ def standardize_path(path):
|
|||||||
|
|
||||||
|
|
||||||
def get_new_wallet_name(wallet_folder: str) -> str:
|
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
|
i = 1
|
||||||
while True:
|
while True:
|
||||||
filename = "wallet_%d" % i
|
filename = "wallet_%d" % i
|
||||||
|
|||||||
Reference in New Issue
Block a user