diff --git a/app/gui.py b/app/gui.py index 3d3de23..0d2880f 100644 --- a/app/gui.py +++ b/app/gui.py @@ -969,10 +969,11 @@ class ScrapeSearchWorker(QObject): results = Signal(list) # candidates with thumb_bytes added finished = Signal() - def __init__(self, system, query): + def __init__(self, system, query, gen): super().__init__() self.system = system self.query = query + self.gen = gen self._cancel = threading.Event() def cancel(self): @@ -1003,11 +1004,12 @@ class ScrapeFetchWorker(QObject): result = Signal(dict) # {"meta": {...}, "cover": bytes|None} finished = Signal() - def __init__(self, system, rid, candidate): + def __init__(self, system, rid, candidate, gen): super().__init__() self.system = system self.rid = rid self.candidate = candidate + self.gen = gen self._cancel = threading.Event() def cancel(self): @@ -1122,17 +1124,17 @@ class ScrapeDialog(QDialog): self.list.clear() self.candidates = [] thread = QThread(self) - worker = ScrapeSearchWorker(self.system, query) + worker = ScrapeSearchWorker(self.system, query, gen) worker.moveToThread(thread) thread.started.connect(worker.run) - worker.results.connect(lambda r, g=gen: self._on_results(r, g)) + worker.results.connect(self._on_results) worker.finished.connect(thread.quit) self._threads.append(thread) self._workers.append(worker) thread.start() - def _on_results(self, results, gen): - if gen != self._search_gen: + def _on_results(self, results): + if getattr(self.sender(), "gen", None) != self._search_gen: return self.search_btn.setEnabled(True) self.candidates = results @@ -1172,17 +1174,17 @@ class ScrapeDialog(QDialog): self.search_btn.setEnabled(False) self.status.setText("Scraping…") thread = QThread(self) - worker = ScrapeFetchWorker(self.system, self.rid, candidate) + worker = ScrapeFetchWorker(self.system, self.rid, candidate, gen) worker.moveToThread(thread) thread.started.connect(worker.run) - worker.result.connect(lambda p, g=gen: self._on_scraped(p, g)) + worker.result.connect(self._on_scraped) worker.finished.connect(thread.quit) self._threads.append(thread) self._workers.append(worker) thread.start() - def _on_scraped(self, payload, gen): - if gen != self._search_gen: + def _on_scraped(self, payload): + if getattr(self.sender(), "gen", None) != self._search_gen: return meta = payload.get("meta") or {} cover = payload.get("cover") @@ -1198,7 +1200,15 @@ class ScrapeDialog(QDialog): self.search_btn.setEnabled(True) self.status.setText("Could not fetch anything for that selection.") - def closeEvent(self, event): + def _shutdown_threads(self): + """Cancel workers and wait for their threads to fully finish. + + Must run before the dialog is destroyed — QThread children abort the + process with "QThread: Destroyed while thread is still running" if they + are deleted while their worker thread is alive. ``done()`` covers + accept()/reject() (including the accept() in ``_on_scraped``); the + close event covers the window-manager close path. + """ for w in self._workers: w.cancel() for t in self._threads: @@ -1207,8 +1217,15 @@ class ScrapeDialog(QDialog): t.wait() self._workers = [] self._threads = [] + + def closeEvent(self, event): + self._shutdown_threads() event.accept() + def done(self, result): + self._shutdown_threads() + super().done(result) + class SettingsPage(QWidget): saved = Signal() @@ -4856,12 +4873,11 @@ class MainWindow(QMainWindow): thread = QThread(self) worker = LibraryWorker(self._ps1_dir, self._ps1_output_dir, self._nps_dir) worker.moveToThread(thread) + thread._worker = worker thread.started.connect(worker.run) worker.result.connect(self._on_library_loaded) worker.finished.connect(thread.quit) - worker.finished.connect(worker.deleteLater) - thread.finished.connect(thread.deleteLater) - thread.finished.connect(lambda t=thread, w=worker: self._forget_library_thread(t, w)) + thread.finished.connect(self._forget_library_thread) self._library_threads.append(thread) self._library_workers.append(worker) thread.start() @@ -4875,11 +4891,14 @@ class MainWindow(QMainWindow): self.store_page.set_library_items(self._library_cache) self.statusBar().showMessage("") - def _forget_library_thread(self, thread, worker): + def _forget_library_thread(self): + thread = self.sender() + worker = getattr(thread, "_worker", None) if thread in self._library_threads: self._library_threads.remove(thread) - if worker in self._library_workers: + if worker is not None and worker in self._library_workers: self._library_workers.remove(worker) + thread.deleteLater() def _apply_library_filter(self): if not hasattr(self, "_library_cache"): @@ -5012,21 +5031,23 @@ class MainWindow(QMainWindow): thread = QThread(self) worker = MetadataWorker(title, gen, system, rid) worker.moveToThread(thread) + thread._worker = worker thread.started.connect(worker.run) worker.result.connect(self._on_metadata) - worker.finished.connect(worker.deleteLater) worker.finished.connect(thread.quit) - thread.finished.connect(thread.deleteLater) - thread.finished.connect(lambda t=thread, w=worker: self._forget_meta_thread(t, w)) + thread.finished.connect(self._forget_meta_thread) self._meta_threads.append(thread) self._meta_workers.append(worker) thread.start() - def _forget_meta_thread(self, thread, worker): + def _forget_meta_thread(self): + thread = self.sender() + worker = getattr(thread, "_worker", None) if thread in self._meta_threads: self._meta_threads.remove(thread) - if worker in self._meta_workers: + if worker is not None and worker in self._meta_workers: self._meta_workers.remove(worker) + thread.deleteLater() def _on_metadata(self, gen, meta): if gen != self._meta_gen: