diff --git a/apps/media_library/models.py b/apps/media_library/models.py index 5b6979e9..2c0528bb 100644 --- a/apps/media_library/models.py +++ b/apps/media_library/models.py @@ -286,6 +286,45 @@ class LibraryScan(models.Model): "artwork": ("artwork_status", "artwork_total", "artwork_processed"), } + def _ensure_completed(self) -> bool: + """ + Bring the overall scan status in sync with stage completion. + + Returns True when the scan transitions to the completed state. + """ + if self.status == self.STATUS_COMPLETED and self.finished_at: + return False + + metadata_done = self.metadata_status in ( + self.STAGE_STATUS_COMPLETED, + self.STAGE_STATUS_SKIPPED, + ) + artwork_done = self.artwork_status in ( + self.STAGE_STATUS_COMPLETED, + self.STAGE_STATUS_SKIPPED, + ) + discovery_done = self.discovery_status == self.STAGE_STATUS_COMPLETED + + if not (metadata_done and artwork_done and discovery_done): + return False + + summary = self.summary or "" + self.mark_completed(summary=summary) + + if self.library_id: + now = timezone.now() + Library.objects.filter(pk=self.library_id).update( + last_scan_at=now, + last_successful_scan_at=now, + updated_at=now, + ) + if getattr(self, "library", None): + self.library.last_scan_at = now + self.library.last_successful_scan_at = now + self.library.updated_at = now + + return True + def stage_snapshot(self, *, normalize: bool = False) -> dict[str, dict[str, int | str]]: """ Return the current stage progress for the scan. @@ -295,6 +334,9 @@ class LibraryScan(models.Model): progress bars consistent in the UI without affecting the stored values that drive the underlying workflow. """ + # Keeping overall status consistent avoids stale "running" badges in the UI. + self._ensure_completed() + snapshot: dict[str, dict[str, int | str]] = {} total_files = max(0, int(self.total_files or 0)) diff --git a/apps/media_library/tasks.py b/apps/media_library/tasks.py index 517479a4..771ac9f2 100644 --- a/apps/media_library/tasks.py +++ b/apps/media_library/tasks.py @@ -293,8 +293,13 @@ def _emit_scan_update(scan: LibraryScan, *, status: str, extra: dict | None = No library_name = "" if getattr(scan, "library", None): library_name = scan.library.name + + resolved_status = status + if status != LibraryScan.STATUS_COMPLETED and scan.status == LibraryScan.STATUS_COMPLETED: + resolved_status = LibraryScan.STATUS_COMPLETED + payload = { - "status": status, + "status": resolved_status, "scan_id": str(scan.id), "library_id": scan.library_id, "library_name": library_name, @@ -427,30 +432,7 @@ def _maybe_mark_scan_completed(scan: LibraryScan) -> None: scan.record_stage_progress("artwork", status=LibraryScan.STAGE_STATUS_COMPLETED) scan.artwork_status = LibraryScan.STAGE_STATUS_COMPLETED - metadata_done = scan.metadata_status in ( - LibraryScan.STAGE_STATUS_COMPLETED, - LibraryScan.STAGE_STATUS_SKIPPED, - ) - artwork_done = scan.artwork_status in ( - LibraryScan.STAGE_STATUS_COMPLETED, - LibraryScan.STAGE_STATUS_SKIPPED, - ) - discovery_done = scan.discovery_status == LibraryScan.STAGE_STATUS_COMPLETED - - if discovery_done and metadata_done and artwork_done: - summary = scan.summary or "" - scan.mark_completed(summary=summary) - if scan.library_id: - now = timezone.now() - Library.objects.filter(pk=scan.library_id).update( - last_scan_at=now, - last_successful_scan_at=now, - updated_at=now, - ) - if getattr(scan, "library", None): - scan.library.last_scan_at = now - scan.library.last_successful_scan_at = now - scan.library.updated_at = now + if scan._ensure_completed(): _emit_scan_update(scan, status="completed")