feat(library): lazy materialization foundation for remote tracks (§Phase1)
Adds nullable storage fields + availability column on tracks, remote source/source_id identity on albums/artists, TrackRepository.materialize() and get_or_create_remote() repos — groundwork for on-demand YTM library (placeholders saved without audio, materialized in-place on first play). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from app.domain.entities.storage import FormatBreakdown, LibraryStats
|
||||
from app.domain.entities.track import Track
|
||||
from app.domain.errors import NotFoundError
|
||||
from app.infrastructure.db.models.artist import ArtistModel
|
||||
from app.infrastructure.db.models.enums import TrackAvailability
|
||||
from app.infrastructure.db.models.track import TrackModel
|
||||
|
||||
|
||||
@@ -31,6 +32,7 @@ def _to_entity(row: TrackModel) -> Track:
|
||||
metadata_status=row.metadata_status,
|
||||
metadata_error=row.metadata_error,
|
||||
enriched_at=row.enriched_at,
|
||||
availability=row.availability,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
)
|
||||
@@ -61,13 +63,14 @@ class SqlAlchemyTrackRepository:
|
||||
id: uuid.UUID,
|
||||
title: str,
|
||||
artist_id: uuid.UUID,
|
||||
storage_uri: str,
|
||||
file_format: str,
|
||||
file_size: int,
|
||||
storage_uri: str | None,
|
||||
file_format: str | None,
|
||||
file_size: int | None,
|
||||
source: str,
|
||||
source_id: str,
|
||||
metadata_status: str,
|
||||
added_by: uuid.UUID | None,
|
||||
availability: str = TrackAvailability.LOCAL.value,
|
||||
) -> Track:
|
||||
row = TrackModel(
|
||||
id=id,
|
||||
@@ -80,12 +83,38 @@ class SqlAlchemyTrackRepository:
|
||||
source_id=source_id,
|
||||
metadata_status=metadata_status,
|
||||
added_by=added_by,
|
||||
availability=availability,
|
||||
)
|
||||
self._session.add(row)
|
||||
await self._session.flush()
|
||||
await self._session.refresh(row)
|
||||
return _to_entity(row)
|
||||
|
||||
async def materialize(
|
||||
self,
|
||||
track_id: uuid.UUID,
|
||||
*,
|
||||
storage_uri: str,
|
||||
file_format: str,
|
||||
file_size: int,
|
||||
bitrate: int | None,
|
||||
) -> Track:
|
||||
"""Fill in a remote placeholder's audio fields after a download (lazy
|
||||
materialization). ``track.id`` is unchanged, so likes/playlists/queue
|
||||
entries that already reference it keep working."""
|
||||
row = await self._session.get(TrackModel, track_id)
|
||||
if row is None:
|
||||
raise NotFoundError(f"Track {track_id} not found.")
|
||||
row.storage_uri = storage_uri
|
||||
row.file_format = file_format
|
||||
row.file_size = file_size
|
||||
if bitrate is not None:
|
||||
row.bitrate = bitrate
|
||||
row.availability = TrackAvailability.LOCAL.value
|
||||
await self._session.flush()
|
||||
await self._session.refresh(row)
|
||||
return _to_entity(row)
|
||||
|
||||
async def delete(self, track_id: uuid.UUID) -> None:
|
||||
row = await self._session.get(TrackModel, track_id)
|
||||
if row is not None:
|
||||
@@ -130,6 +159,7 @@ class SqlAlchemyTrackRepository:
|
||||
func.count(TrackModel.id),
|
||||
func.coalesce(func.sum(TrackModel.file_size), 0),
|
||||
)
|
||||
.where(TrackModel.file_format.is_not(None))
|
||||
.group_by(TrackModel.file_format)
|
||||
.order_by(func.sum(TrackModel.file_size).desc())
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user