fix(path-resolve): CWD-independent base dirs + quality-scan resolve diagnostic

The quality job still resolved 0/18 because the shared resolver kept relative
config paths ("./Transfer") as-is and gated them behind os.path.isdir("./Transfer"),
which only holds when the calling thread's CWD is the app root. The repair
worker thread's CWD isn't guaranteed to be /app, so base_dirs came back empty
and every track was "unresolved".

- _collect_base_dirs now also adds os.path.abspath() of every relative
  candidate, so "./Transfer" → "/app/Transfer" regardless of CWD.
- quality_upgrade_scanner logs a one-shot [QualityResolve] diagnostic on the
  first unresolved track (cwd, transfer_folder + abspath + isdir, base dirs
  tried, abs-join existence) so any remaining mount mismatch is pinpointable
  instead of a silent "all skipped".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dev 2026-06-15 14:39:18 +02:00
parent 5b3061ee2e
commit 973d28f61d
2 changed files with 49 additions and 2 deletions

View file

@ -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)

View file

@ -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: