diff --git a/amcr_viewer/amcr_dialog.py b/amcr_viewer/amcr_dialog.py index a91cc0a..2c3a154 100644 --- a/amcr_viewer/amcr_dialog.py +++ b/amcr_viewer/amcr_dialog.py @@ -7,6 +7,7 @@ from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout, from qgis.PyQt.QtCore import Qt, QSettings from qgis.core import (QgsTask, QgsApplication, QgsMessageLog, Qgis, QgsAuthMethodConfig) +from qgis.utils import iface from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE, OKRESY, KATASTRY, VEDOUCI, PIAN_PRESNOST, TYP_LOKALITY, DRUH_LOKALITY, JISTOTA, @@ -14,6 +15,12 @@ from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE, download_heslare, refresh_globals) +# Keep Python references to running tasks. QgsTaskManager only holds the +# C++ object; without a Python-side reference the wrapper can be garbage +# collected before the task finishes, which crashes QGIS. +_ACTIVE_TASKS = [] + + class UpdateCodelistsTask(QgsTask): def __init__(self, description): super().__init__(description, QgsTask.CanCancel) @@ -370,21 +377,38 @@ class AmcrFilterDialog(QDialog): return row_widget def action_update_heslare(self): - # Create the task instance + # Create the task instance and keep a reference so the Python + # wrapper survives until the task finishes task = UpdateCodelistsTask("Aktualizace heslářů AMČR") + _ACTIVE_TASKS.append(task) - # Re-enable the button regardless of the outcome - task.taskCompleted.connect(lambda: self.btn_update.setEnabled(True)) - task.taskTerminated.connect(lambda: self.btn_update.setEnabled(True)) + # Prevent parallel downloads overwriting heslar.csv + self.btn_update.setEnabled(False) - task.taskCompleted.connect(lambda: QMessageBox.information( - self, - "Hotovo", - "Hesláře byly úspěšně aktualizovány." - )) + # Message boxes are parented to the main window, not to this dialog – + # the dialog may already be closed (and its C++ object deleted) + # by the time the minute-long task finishes. + parent_win = iface.mainWindow() if iface else None + + def _cleanup(): + if task in _ACTIVE_TASKS: + _ACTIVE_TASKS.remove(task) + try: + self.btn_update.setEnabled(True) + except RuntimeError: + pass # dialog already closed + + def on_completed(): + _cleanup() + QMessageBox.information( + parent_win, + "Hotovo", + "Hesláře byly úspěšně aktualizovány." + ) # Show the exact error if the task fails def on_error(): + _cleanup() if task.exception: # This will show exactly what went wrong (e.g. PermissionError) msg = ( @@ -393,8 +417,9 @@ class AmcrFilterDialog(QDialog): ) else: msg = "Aktualizace byla zrušena uživatelem." - QMessageBox.warning(self, "Chyba / Zrušeno", msg) + QMessageBox.warning(parent_win, "Chyba / Zrušeno", msg) + task.taskCompleted.connect(on_completed) task.taskTerminated.connect(on_error) QgsApplication.taskManager().addTask(task) diff --git a/amcr_viewer/amcr_viewer.py b/amcr_viewer/amcr_viewer.py index ac1e44c..e28de5b 100644 --- a/amcr_viewer/amcr_viewer.py +++ b/amcr_viewer/amcr_viewer.py @@ -6,7 +6,6 @@ from qgis.core import Qgis from .amcr_tools import load_amcr_data, login_to_api from .amcr_dialog import AmcrFilterDialog, LoginDialog -from .resources import * import os.path @@ -24,8 +23,9 @@ class AmcrViewer: self.iface = iface self.plugin_dir = os.path.dirname(__file__) - # Determine the user's locale to load appropriate translation files - locale = QSettings().value('locale/userLocale')[0:2] + # Determine the user's locale to load appropriate translation files. + # The setting may be missing (None) on a fresh QGIS install. + locale = str(QSettings().value('locale/userLocale') or 'en')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n',