fix: robust NC activity filter, title extraction, scan result detail
Watcher: - Accept both NC 22+ (type="file_created") and older NC (subject="created_self") so the upload filter works across all Nextcloud versions - Add .opus to audio_extensions - Fix tests: set nc.username on mocks, use realistic activity dicts with type field - Add tests for old NC style, non-band path filter, normalize_nc_path, cursor advance API: - Fix internal.py title extraction: always use filename stem (was using parts[-2] for >3-part paths, which gave folder name instead of song title) - nc-scan now returns NcScanResult with folder, files_found, imported, skipped counts instead of bare song list — gives the UI actionable feedback Web: - Show rich scan result message: folder scanned, count imported, count already registered Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,12 +53,17 @@ async def nc_upload(
|
||||
log.warning("nc-upload: band slug '%s' not found in DB", band_slug)
|
||||
return {"status": "skipped", "reason": "band not found"}
|
||||
|
||||
# Determine song title and folder from remaining path segments
|
||||
# e.g. bands/my-band/songs/session1/rec.mp3 → folder=bands/my-band/songs/session1/, title=session1
|
||||
# e.g. bands/my-band/rec.mp3 → folder=bands/my-band/, title=rec
|
||||
# Determine song title and folder from path.
|
||||
# The title is always the filename stem (e.g. "take1" from "take1.wav").
|
||||
# The nc_folder groups all versions of the same recording (the parent directory).
|
||||
#
|
||||
# Examples:
|
||||
# bands/my-band/take1.wav → folder=bands/my-band/, title=take1
|
||||
# bands/my-band/231015/take1.wav → folder=bands/my-band/231015/, title=take1
|
||||
# bands/my-band/songs/groove/take1.wav → folder=bands/my-band/songs/groove/, title=take1
|
||||
parent = str(Path(path).parent)
|
||||
nc_folder = parent.rstrip("/") + "/"
|
||||
title = Path(path).stem if len(parts) == 3 else parts[-2]
|
||||
title = Path(path).stem
|
||||
|
||||
version_repo = AudioVersionRepository(session)
|
||||
if event.nc_file_etag and await version_repo.get_by_etag(event.nc_file_etag):
|
||||
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from rehearsalhub.db.engine import get_session
|
||||
@@ -25,6 +26,14 @@ router = APIRouter(tags=["songs"])
|
||||
AUDIO_EXTENSIONS = {".mp3", ".wav", ".flac", ".ogg", ".m4a", ".aac", ".opus"}
|
||||
|
||||
|
||||
class NcScanResult(BaseModel):
|
||||
folder: str
|
||||
files_found: int
|
||||
imported: int
|
||||
skipped: int
|
||||
songs: list[SongRead]
|
||||
|
||||
|
||||
@router.get("/bands/{band_id}/songs", response_model=list[SongRead])
|
||||
async def list_songs(
|
||||
band_id: uuid.UUID,
|
||||
@@ -65,7 +74,7 @@ async def create_song(
|
||||
return read
|
||||
|
||||
|
||||
@router.post("/bands/{band_id}/nc-scan", response_model=list[SongRead])
|
||||
@router.post("/bands/{band_id}/nc-scan", response_model=NcScanResult)
|
||||
async def scan_nextcloud(
|
||||
band_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
@@ -98,7 +107,8 @@ async def scan_nextcloud(
|
||||
return href[len(dav_prefix):]
|
||||
return href.lstrip("/")
|
||||
|
||||
imported: list[SongRead] = []
|
||||
imported_songs: list[SongRead] = []
|
||||
skipped_count = 0
|
||||
band_folder = band.nc_folder_path or f"bands/{band.slug}/"
|
||||
|
||||
log.info("Starting NC scan for band '%s' in folder '%s'", band.slug, band_folder)
|
||||
@@ -159,6 +169,7 @@ async def scan_nextcloud(
|
||||
|
||||
if etag and await version_repo.get_by_etag(etag):
|
||||
log.debug("Skipping '%s' — etag already registered", nc_file_path)
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Find or create song record
|
||||
@@ -191,12 +202,21 @@ async def scan_nextcloud(
|
||||
|
||||
read = SongRead.model_validate(song)
|
||||
read.version_count = 1
|
||||
imported.append(read)
|
||||
imported_songs.append(read)
|
||||
label_info = f" [rehearsal: {rehearsal_label}]" if rehearsal_label else ""
|
||||
log.info("Imported '%s' as song '%s'%s", nc_file_path, song_title, label_info)
|
||||
|
||||
log.info("NC scan complete: %d new versions imported", len(imported))
|
||||
return imported
|
||||
log.info(
|
||||
"NC scan complete for '%s': %d imported, %d skipped (already registered)",
|
||||
band_folder, len(imported_songs), skipped_count,
|
||||
)
|
||||
return NcScanResult(
|
||||
folder=band_folder,
|
||||
files_found=len(to_import),
|
||||
imported=len(imported_songs),
|
||||
skipped=skipped_count,
|
||||
songs=imported_songs,
|
||||
)
|
||||
|
||||
|
||||
# ── Comments ──────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user