fix: scan visibility, NC folder validation, watcher logging

- nc-scan: detailed INFO logging of every path found, subfolder
  contents and skip reasons; 502 now includes the exact folder and
  error so user sees a real message instead of a blank result
- band creation: if nc_base_path is explicitly given, verify the
  folder exists in Nextcloud before saving — returns 422 with a
  clear message to the user; auto-generated paths still do MKCOL
- songs search: add ?unattributed=true to return songs with no
  session_id (files not in a YYMMDD folder)
- BandPage: show "Unattributed Recordings" section below sessions
  so scanned files without a dated folder always appear
- watcher event_loop: promote all per-activity log lines from DEBUG
  to INFO so they're visible in default Docker Compose log output;
  log normalized path and skip reason for every activity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Steffen Schuhmann
2026-03-29 14:11:07 +02:00
parent 25502458d0
commit dc6dd9dcfd
6 changed files with 128 additions and 34 deletions

View File

@@ -106,7 +106,7 @@ async def poll_once(nc_client: NextcloudWatcherClient, settings: WatcherSettings
activities = await nc_client.get_activities(since_id=_last_activity_id)
if not activities:
log.debug("No new activities since id=%d", _last_activity_id)
log.info("No new activities since id=%d", _last_activity_id)
return
log.info("Received %d activities (since id=%d)", len(activities), _last_activity_id)
@@ -117,44 +117,42 @@ async def poll_once(nc_client: NextcloudWatcherClient, settings: WatcherSettings
subject = activity.get("subject", "")
raw_path = extract_nc_file_path(activity)
log.debug(
# Advance the cursor regardless of whether we act on this event
_last_activity_id = max(_last_activity_id, activity_id)
log.info(
"Activity id=%d type=%r subject=%r raw_path=%r",
activity_id, activity_type, subject, raw_path,
)
# Advance the cursor regardless of whether we act on this event
_last_activity_id = max(_last_activity_id, activity_id)
if raw_path is None:
log.debug("Skipping activity %d: no file path in payload", activity_id)
log.info(" → skip: no file path in activity payload")
continue
nc_path = normalize_nc_path(raw_path, nc_client.username)
log.info(" → normalized path: %r", nc_path)
# Only care about audio files — skip everything else immediately
if not is_audio_file(nc_path, settings.audio_extensions):
log.debug(
"Skipping activity %d: '%s' is not an audio file (ext: %s)",
activity_id, nc_path, Path(nc_path).suffix.lower(),
log.info(
" → skip: not an audio file (ext=%s)",
Path(nc_path).suffix.lower() or "<none>",
)
continue
if not is_band_audio_path(nc_path):
log.debug(
"Skipping activity %d: '%s' is not inside a band folder",
activity_id, nc_path,
)
log.info(" → skip: path not inside a bands/ folder")
continue
if activity_type not in _UPLOAD_TYPES and subject not in _UPLOAD_SUBJECTS:
log.debug(
"Skipping activity %d: type=%r subject=%r not a file upload event",
activity_id, activity_type, subject,
log.info(
" → skip: type=%r subject=%r is not a file upload event",
activity_type, subject,
)
continue
log.info("Detected audio upload: %s (activity %d)", nc_path, activity_id)
log.info(" → MATCH — registering audio upload: %s", nc_path)
etag = await nc_client.get_file_etag(nc_path)
success = await register_version_with_api(nc_path, etag, settings.api_url)
if not success:
log.warning("Failed to register upload for activity %d (%s)", activity_id, nc_path)
log.warning(" → FAILED to register upload for activity %d (%s)", activity_id, nc_path)