diff --git a/core/library/path_resolver.py b/core/library/path_resolver.py index 0e40e678..6ff3e6b5 100644 --- a/core/library/path_resolver.py +++ b/core/library/path_resolver.py @@ -138,10 +138,25 @@ def _collect_base_dirs( except Exception as e: logger.debug("music paths read failed: %s", e) + # Normalize to absolute forms so resolution does NOT depend on the calling + # thread's CWD. A relative config like "./Transfer" otherwise only resolves + # when os.path.isdir("./Transfer") happens to be true from the current CWD — + # which fails in background workers whose CWD isn't the app root, leaving + # base_dirs empty and every track "unresolved". For each candidate we try + # the raw form first (cheap, preserves an already-absolute path), then its + # os.path.abspath() form so "./Transfer" → "/app/Transfer". + expanded: list[str] = [] + for c in candidates: + if not c: + continue + expanded.append(c) + if not os.path.isabs(c): + expanded.append(os.path.abspath(c)) + # De-duplicate while preserving order, drop empties / non-existent dirs. seen: set[str] = set() out: list[str] = [] - for c in candidates: + for c in expanded: if not c or c in seen: continue seen.add(c) diff --git a/core/repair_jobs/quality_upgrade_scanner.py b/core/repair_jobs/quality_upgrade_scanner.py index 9919ae1c..5a0c5da8 100644 --- a/core/repair_jobs/quality_upgrade_scanner.py +++ b/core/repair_jobs/quality_upgrade_scanner.py @@ -85,14 +85,22 @@ class QualityUpgradeScannerJob(RepairJob): context.update_progress(0, total) probe_failed = 0 + _diag_logged = False for i, (track_id, info) in enumerate(track_list): if context.check_stop(): return result if i % 20 == 0 and context.wait_if_paused(): return result - resolved = self._resolve_path(info.get('file_path', ''), context) + raw_fp = info.get('file_path', '') + resolved = self._resolve_path(raw_fp, context) if not resolved: + # One-shot diagnostic on the first unresolved track — logs EXACTLY + # what the resolver tried (base dirs, cwd) so a path/mount mismatch + # is diagnosable instead of a silent "all skipped". + if not _diag_logged: + _diag_logged = True + self._log_resolve_diag(raw_fp, context) result.skipped += 1 probe_failed += 1 continue @@ -235,6 +243,30 @@ class QualityUpgradeScannerJob(RepairJob): config_manager=context.config_manager, ) + def _log_resolve_diag(self, file_path, context): + """Log a detailed diagnostic for the first track whose path can't be + resolved — the only reliable way to tell apart a CWD problem, a wrong + transfer mount, or genuinely-missing files in this container.""" + from core.library.path_resolver import resolve_library_file_path_with_diagnostic + try: + _, attempt = resolve_library_file_path_with_diagnostic( + file_path, + transfer_folder=context.transfer_folder, + config_manager=context.config_manager, + ) + tf = context.transfer_folder + abs_tf = os.path.abspath(tf) if tf else None + logger.warning( + "[QualityResolve] unresolved db_path=%r | cwd=%r | transfer_folder=%r " + "(abspath=%r, isdir=%s) | base_dirs_tried=%r | abs_join_exists=%s", + file_path, os.getcwd(), tf, abs_tf, + os.path.isdir(abs_tf) if abs_tf else None, + attempt.base_dirs_tried, + os.path.exists(os.path.join(abs_tf, file_path)) if abs_tf else None, + ) + except Exception as e: + logger.warning("[QualityResolve] diagnostic failed: %s", e) + def estimate_scope(self, context: JobContext) -> int: conn = None try: