feat: incremental SSE scan, recursive NC traversal, custom folder support

- nc_scan.py: recursive collect_audio_files (fixes depth-1 bug); scan_band_folder
  yields ndjson events (progress/song/session/skipped/done) for streaming
- songs.py: replace old flat scan with scan_band_folder; add GET nc-scan/stream
  endpoint using _member_from_request so ?token= auth works for fetch-based SSE
- BandPage.tsx: scan button now consumes ndjson stream via fetch+ReadableStream;
  sessions/unattributed invalidated as each song/session event arrives
- session.py: add extract_session_folder() for YYMMDD path extraction
- rehearsal_session.py: get_or_create uses begin_nested() savepoint to handle races
- band.py: add get_by_nc_folder_prefix() for custom nc_folder_path band lookup
- internal.py: nc-upload falls back to prefix match when slug lookup fails
- event_loop.py: remove hardcoded bands/ guard; let internal API handle filtering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Steffen Schuhmann
2026-03-29 15:09:42 +02:00
parent dc6dd9dcfd
commit 7cad3e544a
8 changed files with 393 additions and 204 deletions

View File

@@ -44,15 +44,20 @@ async def nc_upload(
if Path(path).suffix.lower() not in AUDIO_EXTENSIONS:
return {"status": "skipped", "reason": "not an audio file"}
parts = path.split("/")
if len(parts) < 3 or parts[0] != "bands":
return {"status": "skipped", "reason": "path not under bands/"}
band_slug = parts[1]
band_repo = BandRepository(session)
band = await band_repo.get_by_slug(band_slug)
# Try slug-based lookup first (standard bands/{slug}/ layout)
parts = path.split("/")
band = None
if len(parts) >= 3 and parts[0] == "bands":
band = await band_repo.get_by_slug(parts[1])
# Fall back to prefix match for bands with custom nc_folder_path
if band is None:
log.warning("nc-upload: band slug '%s' not found in DB", band_slug)
band = await band_repo.get_by_nc_folder_prefix(path)
if band is None:
log.info("nc-upload: no band found for path '%s' — skipping", path)
return {"status": "skipped", "reason": "band not found"}
# Determine song title and folder from path.