diff --git a/src/cargomanager.py b/src/cargomanager.py index c1b6360..3154335 100644 --- a/src/cargomanager.py +++ b/src/cargomanager.py @@ -413,7 +413,13 @@ def get_cargo_data( if cargo_data is not None: store_json(cargo_data, cargo_file) return cargo_data - # TODO what happens when both backups fail? + # Download failed and no usable backup exists (typical on a + # fresh install when the wiki/GitHub cache is unreachable). + # Raise so the caller surfaces a real error instead of silently + # returning None — which would crash with TypeError further up. + raise RuntimeError( + f"Could not obtain cargo table '{filename}': download " + f"failed and no backup is available.") else: return self.get_cargo_data(filename, url, ignore_cache_age=True) else: diff --git a/src/downloader.py b/src/downloader.py index 284cf77..7024081 100644 --- a/src/downloader.py +++ b/src/downloader.py @@ -2,7 +2,7 @@ from os import getenv as os__getenv from pathlib import Path from requests import Session -from requests.exceptions import Timeout +from requests.exceptions import RequestException from time import time from threading import Thread from typing import Callable @@ -93,7 +93,7 @@ def fetch_json(self, url: str) -> dict | list | None: response.encoding = 'utf-8' return json__loads(compensate_json(response.text)) return None - except (Timeout, JSONDecodeError): + except (RequestException, JSONDecodeError, ValueError): return None def download_cargo_table(self, url: str, file_name: str) -> dict | list | None: diff --git a/src/widgets.py b/src/widgets.py index d9b497c..de8336f 100644 --- a/src/widgets.py +++ b/src/widgets.py @@ -296,6 +296,7 @@ class Thread(QThread): """ result: Signal = Signal(object) done: Signal = Signal() + error: Signal = Signal(object) def __init__(self, target: Callable, args: tuple = (), kwargs: dict[str] = {}): super().__init__() @@ -318,9 +319,19 @@ def set_args(self, new_args: tuple) -> bool: def run(self): """ This function will be executed in a separate thread. + + `done` is always emitted, even if the target raises, so callers waiting + on it (e.g. the splash screen) are never left hanging. Exceptions are + re-emitted via `error` so they can be logged or surfaced in the UI. """ - self.result.emit(self._target(*self._args, **self._kwargs)) - self.done.emit() + try: + self.result.emit(self._target(*self._args, **self._kwargs)) + except BaseException as exc: + import traceback + traceback.print_exc() + self.error.emit(exc) + finally: + self.done.emit() class ShipButton(QLabel):