From 1cce216c1f44149010acf31f9d36eb2c010689af Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 28 Apr 2025 16:55:21 +0200 Subject: [PATCH] fix: prevent PluginsDialog from getting in bad state If the plugin file got already deleted while being in the installation dialog, trying to delete it again will raise an exception. This is fixed by catching the exception. If the user tries to install an external plugin that is already installed, and then closes the PluginDialog, the PluginsDialog will get into a bad state, throwing an exeption when opening it. This happens because the add_plugin_dialog deletes the zipfile if the user closes or cancels the installation dialog. This is fixed by checking if the plugin is already existing, instead of trying to install an already existing plugin. --- electrum/gui/qt/plugins_dialog.py | 17 ++++++++++++++--- electrum/plugin.py | 4 +++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qt/plugins_dialog.py b/electrum/gui/qt/plugins_dialog.py index 7b1e3a52f..07d1124e7 100644 --- a/electrum/gui/qt/plugins_dialog.py +++ b/electrum/gui/qt/plugins_dialog.py @@ -7,6 +7,7 @@ from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QWidg from PyQt6.QtCore import Qt from electrum.i18n import _ +from electrum.logging import get_logger from .util import WindowModalDialog, Buttons, CloseButton, WWLabel, insert_spaces, MessageBoxMixin, EnterButton from .util import read_QIcon_from_bytes, IconLabel @@ -88,7 +89,7 @@ class PluginDialog(WindowModalDialog): _('Settings'), partial(p.settings_dialog, self)) buttons.insert(1, settings_button) - # add buttonss + # add buttons vbox.addLayout(Buttons(*buttons)) def do_toggle(self): @@ -150,6 +151,7 @@ class PluginStatusButton(QPushButton): class PluginsDialog(WindowModalDialog, MessageBoxMixin): + _logger = get_logger(__name__) def __init__(self, config: 'SimpleConfig', plugins:'Plugins', *, gui_object: Optional['ElectrumGui'] = None): WindowModalDialog.__init__(self, None, _('Electrum Plugins')) @@ -264,7 +266,10 @@ class PluginsDialog(WindowModalDialog, MessageBoxMixin): self.show_error(f"{e}") success = False if not success: - os.unlink(path) + try: + os.unlink(path) + except FileNotFoundError: + self._logger.debug("", exc_info=True) def add_plugin_dialog(self): pubkey, salt = self.plugins.get_pubkey_bytes() @@ -276,6 +281,9 @@ class PluginsDialog(WindowModalDialog, MessageBoxMixin): return plugins_dir = self.plugins.get_external_plugin_dir() path = os.path.join(plugins_dir, os.path.basename(filename)) + if os.path.exists(path): + self.show_warning(_('Plugin already installed.')) + return shutil.copyfile(filename, path) try: success = self.add_external_plugin(path) @@ -283,7 +291,10 @@ class PluginsDialog(WindowModalDialog, MessageBoxMixin): self.show_error(f"{e}") success = False if not success: - os.unlink(path) + try: + os.unlink(path) + except FileNotFoundError: + self._logger.debug("", exc_info=True) def add_external_plugin(self, path): manifest = self.plugins.read_manifest(path) diff --git a/electrum/plugin.py b/electrum/plugin.py index c2e5cd7dd..068125f22 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -246,10 +246,12 @@ class Plugins(DaemonThread): os.mkdir(pkg_path) return pkg_path - async def download_external_plugin(self, url): + async def download_external_plugin(self, url: str) -> str: filename = os.path.basename(urlparse(url).path) pkg_path = self.get_external_plugin_dir() path = os.path.join(pkg_path, filename) + if os.path.exists(path): + raise FileExistsError(f"Plugin {filename} already exists at {path}") async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: