diff --git a/app/gui.py b/app/gui.py index d0ea5fe..4d6603d 100644 --- a/app/gui.py +++ b/app/gui.py @@ -2670,6 +2670,7 @@ class QueuePage(QWidget): "umd-dump": "UMD dump", "save-sync": "Save sync", "nps-download": "NoPayStation", + "covers": "Covers bundle", } def __init__(self, parent=None): @@ -3809,35 +3810,6 @@ class LibraryWorker(QObject): self.finished.emit() -class CoversBundleWorker(QObject): - """Download + extract the offline cover bundle off the GUI thread.""" - progress = Signal(int, int) # done bytes, total bytes (0 = unknown) - succeeded = Signal() - failed = Signal(str) - finished = Signal() - - def __init__(self): - super().__init__() - self._cancel = threading.Event() - - def cancel(self): - self._cancel.set() - - def run(self): - try: - covers.download_covers_bundle( - progress_cb=lambda d, t: self.progress.emit(d, t), - cancel_event=self._cancel) - except InterruptedError: - pass - except Exception as exc: # noqa: BLE001 - self.failed.emit(str(exc)) - else: - self.succeeded.emit() - finally: - self.finished.emit() - - class SaveSyncPlanWorker(QObject): """Compute the save-sync plan off the GUI thread (content hashing is I/O-heavy).""" plan = Signal(list, bool) # plan, switch_to_queue @@ -5434,6 +5406,9 @@ class MainWindow(QMainWindow): QTimer.singleShot(0, self._refresh_psp) if kind == "save-sync": QTimer.singleShot(0, self._refresh_saves) + if kind == "covers": + covers.clear_miss_cache() + QTimer.singleShot(0, self.load_library) if kind == "ps1-convert": QTimer.singleShot(0, self.ps1_page.refresh) QTimer.singleShot(0, self.load_library) @@ -5796,51 +5771,20 @@ class MainWindow(QMainWindow): self._download_covers_bundle() def _download_covers_bundle(self): - """Download + extract the offline cover bundle with a progress dialog.""" - if self._closing or getattr(self, "_bundle_worker", None) is not None: + """Queue the offline cover bundle download in the transfer queue.""" + if self._closing: return - prog = QProgressDialog("Downloading cover bundle…", "Cancel", 0, 0, self) - prog.setWindowTitle("Offline covers") - prog.setWindowModality(Qt.WindowModal) - prog.setMinimumDuration(0) - prog.setAutoClose(False) - prog.setAutoReset(False) - - result = {"ok": False, "err": ""} - thread = QThread(self) - worker = CoversBundleWorker() - worker.moveToThread(thread) - thread.started.connect(worker.run) - worker.progress.connect(lambda d, t: self._on_bundle_progress(prog, d, t)) - worker.succeeded.connect(lambda: result.update(ok=True)) - worker.failed.connect(lambda e: result.update(err=str(e))) - prog.canceled.connect(worker.cancel) - worker.finished.connect(thread.quit) - thread.finished.connect(lambda: self._on_bundle_done(prog, result, worker, thread)) - self._bundle_worker = worker - self._bundle_thread = thread - thread.start() - - @staticmethod - def _on_bundle_progress(prog, done, total): - if total > 0: - prog.setRange(0, total) - prog.setValue(done) - prog.setLabelText( - f"Downloading cover bundle… {done / (1 << 20):.0f} / {total / (1 << 20):.0f} MB") - else: - prog.setRange(0, 0) - - def _on_bundle_done(self, prog, result, worker, thread): - prog.close() - self._bundle_worker = None - self._bundle_thread = None - if result["ok"]: - QMessageBox.information(self, "Offline covers", "Cover bundle installed.") - covers.clear_miss_cache() - self.load_library() - elif result["err"]: - QMessageBox.warning(self, "Offline covers", f"Download failed:\n{result['err']}") + for j in self.queue.jobs: + if j.kind == "covers" and j.status in ("queued", "running", "cancelling"): + return # already queued or running + self.queue.enqueue( + transfers.CoversBundleSource(), + str(config.COVERS_DIR), + None, + "Offline cover bundle", + kind="covers", + ) + self.tabs.setCurrentWidget(self.queue_page) def closeEvent(self, event): self._closing = True @@ -5850,11 +5794,6 @@ class MainWindow(QMainWindow): self.worker.choose(None) self.worker.cancel() self._thread_cleanup() - if getattr(self, "_bundle_worker", None) is not None: - self._bundle_worker.cancel() - if getattr(self, "_bundle_thread", None) is not None and _thread_running(self._bundle_thread): - self._bundle_thread.quit() - self._bundle_thread.wait() self.cover_grid.shutdown() for t in self._meta_threads: if _thread_running(t): diff --git a/app/transfers.py b/app/transfers.py index c7c82cc..5fd9bf6 100644 --- a/app/transfers.py +++ b/app/transfers.py @@ -175,6 +175,33 @@ class Source: raise NotImplementedError +class CoversBundleSource: + """Download the combined offline cover bundle (runs as a queue job).""" + label = "psppt-covers.zip" + + def __init__(self, url=""): + self.url = url + + def download(self, item, dest_dir, progress_cb=None, cancel_event=None): + from . import covers + + def on_progress(done, total): + if progress_cb is None: + return + frac = (done / total) if total else 0.0 + detail = (f"{done / (1 << 20):.0f} / {total / (1 << 20):.0f} MB" + if total else "") + progress_cb("Downloading", frac, detail) + + try: + result = covers.download_covers_bundle(self.url, + progress_cb=on_progress, + cancel_event=cancel_event) + except InterruptedError: + raise TransferCancelled() from None + return str(result) + + class FileCopySource(Source): """Copy a single local file (library -> PSP or PSP -> library).