Wokring on Nextcloud scan
This commit is contained in:
@@ -12,7 +12,6 @@ from rehearsalhub.config import get_settings
|
|||||||
from rehearsalhub.db.engine import get_session
|
from rehearsalhub.db.engine import get_session
|
||||||
from rehearsalhub.db.models import AudioVersion, BandMember, Member
|
from rehearsalhub.db.models import AudioVersion, BandMember, Member
|
||||||
from rehearsalhub.queue.redis_queue import RedisJobQueue
|
from rehearsalhub.queue.redis_queue import RedisJobQueue
|
||||||
from rehearsalhub.repositories.audio_version import AudioVersionRepository
|
|
||||||
from rehearsalhub.repositories.band import BandRepository
|
from rehearsalhub.repositories.band import BandRepository
|
||||||
from rehearsalhub.repositories.rehearsal_session import RehearsalSessionRepository
|
from rehearsalhub.repositories.rehearsal_session import RehearsalSessionRepository
|
||||||
from rehearsalhub.repositories.song import SongRepository
|
from rehearsalhub.repositories.song import SongRepository
|
||||||
@@ -91,36 +90,40 @@ async def nc_upload(
|
|||||||
if session_folder_path and session_folder_path.rstrip("/") == nc_folder.rstrip("/"):
|
if session_folder_path and session_folder_path.rstrip("/") == nc_folder.rstrip("/"):
|
||||||
nc_folder = nc_folder + title + "/"
|
nc_folder = nc_folder + title + "/"
|
||||||
|
|
||||||
version_repo = AudioVersionRepository(session)
|
|
||||||
if event.nc_file_etag and await version_repo.get_by_etag(event.nc_file_etag):
|
|
||||||
return {"status": "skipped", "reason": "version already registered"}
|
|
||||||
|
|
||||||
# Resolve or create rehearsal session from YYMMDD folder segment
|
# Resolve or create rehearsal session from YYMMDD folder segment
|
||||||
session_repo = RehearsalSessionRepository(session)
|
session_repo = RehearsalSessionRepository(session)
|
||||||
rehearsal_date = parse_rehearsal_date(path)
|
rehearsal_date = parse_rehearsal_date(path)
|
||||||
rehearsal_session_id = None
|
rehearsal_session_id = None
|
||||||
if rehearsal_date:
|
if rehearsal_date:
|
||||||
rehearsal_session = await session_repo.get_or_create(band.id, rehearsal_date, nc_folder)
|
try:
|
||||||
rehearsal_session_id = rehearsal_session.id
|
rehearsal_session = await session_repo.get_or_create(band.id, rehearsal_date, nc_folder)
|
||||||
log.debug("nc-upload: linked to session %s (%s)", rehearsal_session_id, rehearsal_date)
|
rehearsal_session_id = rehearsal_session.id
|
||||||
|
log.debug("nc-upload: linked to session %s (%s)", rehearsal_session_id, rehearsal_date)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error("nc-upload: failed to resolve session for '%s': %s", path, exc, exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to resolve rehearsal session") from exc
|
||||||
|
|
||||||
song_repo = SongRepository(session)
|
song_repo = SongRepository(session)
|
||||||
song = await song_repo.get_by_nc_folder_path(nc_folder)
|
try:
|
||||||
if song is None:
|
song = await song_repo.get_by_nc_folder_path(nc_folder)
|
||||||
song = await song_repo.get_by_title_and_band(band.id, title)
|
if song is None:
|
||||||
if song is None:
|
song = await song_repo.get_by_title_and_band(band.id, title)
|
||||||
song = await song_repo.create(
|
if song is None:
|
||||||
band_id=band.id,
|
song = await song_repo.create(
|
||||||
session_id=rehearsal_session_id,
|
band_id=band.id,
|
||||||
title=title,
|
session_id=rehearsal_session_id,
|
||||||
status="jam",
|
title=title,
|
||||||
notes=None,
|
status="jam",
|
||||||
nc_folder_path=nc_folder,
|
notes=None,
|
||||||
created_by=None,
|
nc_folder_path=nc_folder,
|
||||||
)
|
created_by=None,
|
||||||
log.info("nc-upload: created song '%s' for band '%s'", title, band.slug)
|
)
|
||||||
elif rehearsal_session_id and song.session_id is None:
|
log.info("nc-upload: created song '%s' for band '%s'", title, band.slug)
|
||||||
song = await song_repo.update(song, session_id=rehearsal_session_id)
|
elif rehearsal_session_id and song.session_id is None:
|
||||||
|
song = await song_repo.update(song, session_id=rehearsal_session_id)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error("nc-upload: failed to find/create song for '%s': %s", path, exc, exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to resolve song") from exc
|
||||||
|
|
||||||
# Use first member of the band as uploader (best-effort for watcher uploads)
|
# Use first member of the band as uploader (best-effort for watcher uploads)
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
@@ -137,16 +140,24 @@ async def nc_upload(
|
|||||||
uploader = uploader_result.scalar_one_or_none()
|
uploader = uploader_result.scalar_one_or_none()
|
||||||
storage = NextcloudClient.for_member(uploader) if uploader else None
|
storage = NextcloudClient.for_member(uploader) if uploader else None
|
||||||
|
|
||||||
song_svc = SongService(session, storage=storage)
|
try:
|
||||||
version = await song_svc.register_version(
|
song_svc = SongService(session, storage=storage)
|
||||||
song.id,
|
version = await song_svc.register_version(
|
||||||
AudioVersionCreate(
|
song.id,
|
||||||
nc_file_path=path,
|
AudioVersionCreate(
|
||||||
nc_file_etag=event.nc_file_etag,
|
nc_file_path=path,
|
||||||
format=Path(path).suffix.lstrip(".").lower(),
|
nc_file_etag=event.nc_file_etag,
|
||||||
),
|
format=Path(path).suffix.lstrip(".").lower(),
|
||||||
uploader_id,
|
),
|
||||||
)
|
uploader_id,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error(
|
||||||
|
"nc-upload: failed to register version for '%s' (song '%s'): %s",
|
||||||
|
path, song.title, exc, exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to register version") from exc
|
||||||
|
|
||||||
log.info("nc-upload: registered version %s for song '%s'", version.id, song.title)
|
log.info("nc-upload: registered version %s for song '%s'", version.id, song.title)
|
||||||
return {"status": "ok", "version_id": str(version.id), "song_id": str(song.id)}
|
return {"status": "ok", "version_id": str(version.id), "song_id": str(song.id)}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from urllib.parse import unquote
|
|||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from rehearsalhub.repositories.audio_version import AudioVersionRepository
|
|
||||||
from rehearsalhub.repositories.rehearsal_session import RehearsalSessionRepository
|
from rehearsalhub.repositories.rehearsal_session import RehearsalSessionRepository
|
||||||
from rehearsalhub.repositories.song import SongRepository
|
from rehearsalhub.repositories.song import SongRepository
|
||||||
from rehearsalhub.schemas.audio_version import AudioVersionCreate
|
from rehearsalhub.schemas.audio_version import AudioVersionCreate
|
||||||
@@ -103,7 +102,6 @@ async def scan_band_folder(
|
|||||||
dav_prefix = f"/remote.php/dav/files/{nc._auth[0]}/"
|
dav_prefix = f"/remote.php/dav/files/{nc._auth[0]}/"
|
||||||
relative = _make_relative(dav_prefix)
|
relative = _make_relative(dav_prefix)
|
||||||
|
|
||||||
version_repo = AudioVersionRepository(db_session)
|
|
||||||
session_repo = RehearsalSessionRepository(db_session)
|
session_repo = RehearsalSessionRepository(db_session)
|
||||||
song_repo = SongRepository(db_session)
|
song_repo = SongRepository(db_session)
|
||||||
song_svc = SongService(db_session)
|
song_svc = SongService(db_session)
|
||||||
@@ -133,68 +131,69 @@ async def scan_band_folder(
|
|||||||
meta = await nc.get_file_metadata(nc_file_path)
|
meta = await nc.get_file_metadata(nc_file_path)
|
||||||
etag = meta.etag
|
etag = meta.etag
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.warning("Metadata error for '%s': %s", nc_file_path, exc)
|
log.error("Metadata fetch failed for '%s': %s", nc_file_path, exc, exc_info=True)
|
||||||
|
skipped += 1
|
||||||
yield {"type": "skipped", "path": nc_file_path, "reason": f"metadata error: {exc}"}
|
yield {"type": "skipped", "path": nc_file_path, "reason": f"metadata error: {exc}"}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip if this exact version is already indexed
|
try:
|
||||||
if etag and await version_repo.get_by_etag(etag):
|
# Resolve or create a RehearsalSession from a YYMMDD folder segment
|
||||||
log.info("Already registered (etag match): %s", nc_file_path)
|
rehearsal_date = parse_rehearsal_date(nc_file_path)
|
||||||
skipped += 1
|
rehearsal_session_id = None
|
||||||
yield {"type": "skipped", "path": nc_file_path, "reason": "already registered"}
|
if rehearsal_date:
|
||||||
continue
|
session_folder = extract_session_folder(nc_file_path) or song_folder
|
||||||
|
rs = await session_repo.get_or_create(band_id, rehearsal_date, session_folder)
|
||||||
|
rehearsal_session_id = rs.id
|
||||||
|
yield {
|
||||||
|
"type": "session",
|
||||||
|
"session": {
|
||||||
|
"id": str(rs.id),
|
||||||
|
"date": rs.date.isoformat(),
|
||||||
|
"label": rs.label,
|
||||||
|
"nc_folder_path": rs.nc_folder_path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# Resolve or create a RehearsalSession from a YYMMDD folder segment
|
# Find or create the Song record
|
||||||
rehearsal_date = parse_rehearsal_date(nc_file_path)
|
song = await song_repo.get_by_nc_folder_path(song_folder)
|
||||||
rehearsal_session_id = None
|
if song is None:
|
||||||
if rehearsal_date:
|
song = await song_repo.get_by_title_and_band(band_id, song_title)
|
||||||
session_folder = extract_session_folder(nc_file_path) or song_folder
|
is_new = song is None
|
||||||
rs = await session_repo.get_or_create(band_id, rehearsal_date, session_folder)
|
if is_new:
|
||||||
rehearsal_session_id = rs.id
|
log.info("Creating song '%s' folder='%s'", song_title, song_folder)
|
||||||
yield {
|
song = await song_repo.create(
|
||||||
"type": "session",
|
band_id=band_id,
|
||||||
"session": {
|
session_id=rehearsal_session_id,
|
||||||
"id": str(rs.id),
|
title=song_title,
|
||||||
"date": rs.date.isoformat(),
|
status="jam",
|
||||||
"label": rs.label,
|
notes=None,
|
||||||
"nc_folder_path": rs.nc_folder_path,
|
nc_folder_path=song_folder,
|
||||||
},
|
created_by=member_id,
|
||||||
}
|
)
|
||||||
|
elif rehearsal_session_id and song.session_id is None:
|
||||||
|
song = await song_repo.update(song, session_id=rehearsal_session_id)
|
||||||
|
|
||||||
# Find or create the Song record
|
# Register the audio version
|
||||||
song = await song_repo.get_by_nc_folder_path(song_folder)
|
version = await song_svc.register_version(
|
||||||
if song is None:
|
song.id,
|
||||||
song = await song_repo.get_by_title_and_band(band_id, song_title)
|
AudioVersionCreate(
|
||||||
is_new = song is None
|
nc_file_path=nc_file_path,
|
||||||
if is_new:
|
nc_file_etag=etag,
|
||||||
log.info("Creating song '%s' folder='%s'", song_title, song_folder)
|
format=Path(nc_file_path).suffix.lstrip(".").lower(),
|
||||||
song = await song_repo.create(
|
file_size_bytes=meta.size,
|
||||||
band_id=band_id,
|
),
|
||||||
session_id=rehearsal_session_id,
|
member_id,
|
||||||
title=song_title,
|
|
||||||
status="jam",
|
|
||||||
notes=None,
|
|
||||||
nc_folder_path=song_folder,
|
|
||||||
created_by=member_id,
|
|
||||||
)
|
)
|
||||||
elif rehearsal_session_id and song.session_id is None:
|
log.info("Imported '%s' as version %s for song '%s'", nc_file_path, version.id, song.title)
|
||||||
song = await song_repo.update(song, session_id=rehearsal_session_id)
|
|
||||||
|
|
||||||
# Register the audio version
|
imported += 1
|
||||||
await song_svc.register_version(
|
read = SongRead.model_validate(song).model_copy(update={"version_count": 1, "session_id": rehearsal_session_id})
|
||||||
song.id,
|
yield {"type": "song", "song": read.model_dump(mode="json"), "is_new": is_new}
|
||||||
AudioVersionCreate(
|
|
||||||
nc_file_path=nc_file_path,
|
|
||||||
nc_file_etag=etag,
|
|
||||||
format=Path(nc_file_path).suffix.lstrip(".").lower(),
|
|
||||||
file_size_bytes=meta.size,
|
|
||||||
),
|
|
||||||
member_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
imported += 1
|
except Exception as exc:
|
||||||
read = SongRead.model_validate(song).model_copy(update={"version_count": 1, "session_id": rehearsal_session_id})
|
log.error("Failed to import '%s': %s", nc_file_path, exc, exc_info=True)
|
||||||
yield {"type": "song", "song": read.model_dump(mode="json"), "is_new": is_new}
|
skipped += 1
|
||||||
|
yield {"type": "skipped", "path": nc_file_path, "reason": f"import error: {exc}"}
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
"type": "done",
|
"type": "done",
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
from rehearsalhub.db.models import AudioVersion, Song
|
from rehearsalhub.db.models import AudioVersion, Song
|
||||||
from rehearsalhub.queue.redis_queue import RedisJobQueue
|
from rehearsalhub.queue.redis_queue import RedisJobQueue
|
||||||
from rehearsalhub.repositories.audio_version import AudioVersionRepository
|
from rehearsalhub.repositories.audio_version import AudioVersionRepository
|
||||||
@@ -67,11 +70,6 @@ class SongService:
|
|||||||
data: AudioVersionCreate,
|
data: AudioVersionCreate,
|
||||||
uploader_id: uuid.UUID,
|
uploader_id: uuid.UUID,
|
||||||
) -> AudioVersion:
|
) -> AudioVersion:
|
||||||
if data.nc_file_etag:
|
|
||||||
existing = await self._version_repo.get_by_etag(data.nc_file_etag)
|
|
||||||
if existing:
|
|
||||||
return existing
|
|
||||||
|
|
||||||
version_number = await self._repo.next_version_number(song_id)
|
version_number = await self._repo.next_version_number(song_id)
|
||||||
version = await self._version_repo.create(
|
version = await self._version_repo.create(
|
||||||
song_id=song_id,
|
song_id=song_id,
|
||||||
@@ -85,8 +83,15 @@ class SongService:
|
|||||||
uploaded_by=uploader_id,
|
uploaded_by=uploader_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._queue.enqueue(
|
try:
|
||||||
"transcode",
|
await self._queue.enqueue(
|
||||||
{"version_id": str(version.id), "nc_file_path": data.nc_file_path},
|
"transcode",
|
||||||
)
|
{"version_id": str(version.id), "nc_file_path": data.nc_file_path},
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error(
|
||||||
|
"Failed to enqueue transcode job for version %s ('%s'): %s",
|
||||||
|
version.id, data.nc_file_path, exc, exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
return version
|
return version
|
||||||
|
|||||||
Reference in New Issue
Block a user