feat(android): port cached_artists / cached_albums / cached_tracks (M8 4.2 slice 1)
Mirrors flutter_client/lib/cache/db.dart's CachedArtists / CachedAlbums / CachedTracks Drift tables. Library cache foundation — LibraryRepository (Phase 5.2) reads cache-first through these DAOs and refreshes from server via the sync controller (Phase 12.4). Column names follow Kotlin idiom (camelCase) instead of Drift's snake_case; the schema is internal to the native client and the wire JSON conversion happens in feature-level mappers. Each DAO carries: - observe* (Flow) for cache-first reads in ViewModels - getById/getByIds (suspend) for one-shot lookups - upsertAll (suspend, REPLACE) for sync writes - deleteByIds (suspend) for sync-driven deletes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-4
@@ -3,7 +3,13 @@ package com.fabledsword.minstrel.cache.db
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedAlbumDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedArtistDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.CachedTrackDao
|
||||
import com.fabledsword.minstrel.cache.db.dao.SyncMetadataDao
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedAlbumEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedArtistEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedTrackEntity
|
||||
import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
|
||||
/**
|
||||
@@ -16,14 +22,15 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
* change with explicit `Migration` entries; `fallbackToDestructiveMigration`
|
||||
* in `DatabaseModule` is the safety net while we're pre-v1.
|
||||
*
|
||||
* Most entity families land in Task 4.2 (one commit per Drift table
|
||||
* family). `SyncMetadataEntity` was pulled forward into Task 4.1 to
|
||||
* satisfy Room's "at least one entity required" compile-time check;
|
||||
* its consumer (SyncController) arrives in Phase 12.4.
|
||||
* Entity families land in Task 4.2 slices. The set will grow until the
|
||||
* whole Drift schema is mirrored.
|
||||
*/
|
||||
@Database(
|
||||
entities = [
|
||||
SyncMetadataEntity::class,
|
||||
CachedArtistEntity::class,
|
||||
CachedAlbumEntity::class,
|
||||
CachedTrackEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
@@ -31,4 +38,7 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
|
||||
@TypeConverters(MinstrelTypeConverters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun syncMetadataDao(): SyncMetadataDao
|
||||
abstract fun cachedArtistDao(): CachedArtistDao
|
||||
abstract fun cachedAlbumDao(): CachedAlbumDao
|
||||
abstract fun cachedTrackDao(): CachedTrackDao
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.fabledsword.minstrel.cache.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedAlbumEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedAlbumDao {
|
||||
@Query("SELECT * FROM cached_albums ORDER BY sortTitle COLLATE NOCASE ASC")
|
||||
fun observeAll(): Flow<List<CachedAlbumEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM cached_albums " +
|
||||
"WHERE artistId = :artistId ORDER BY sortTitle COLLATE NOCASE ASC",
|
||||
)
|
||||
fun observeByArtist(artistId: String): Flow<List<CachedAlbumEntity>>
|
||||
|
||||
@Query("SELECT * FROM cached_albums WHERE id = :id")
|
||||
suspend fun getById(id: String): CachedAlbumEntity?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedAlbumEntity>)
|
||||
|
||||
@Query("DELETE FROM cached_albums WHERE id IN (:ids)")
|
||||
suspend fun deleteByIds(ids: List<String>)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.fabledsword.minstrel.cache.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedArtistEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedArtistDao {
|
||||
@Query("SELECT * FROM cached_artists ORDER BY sortName COLLATE NOCASE ASC")
|
||||
fun observeAll(): Flow<List<CachedArtistEntity>>
|
||||
|
||||
@Query("SELECT * FROM cached_artists WHERE id = :id")
|
||||
suspend fun getById(id: String): CachedArtistEntity?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedArtistEntity>)
|
||||
|
||||
@Query("DELETE FROM cached_artists WHERE id IN (:ids)")
|
||||
suspend fun deleteByIds(ids: List<String>)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.fabledsword.minstrel.cache.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.fabledsword.minstrel.cache.db.entities.CachedTrackEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CachedTrackDao {
|
||||
@Query(
|
||||
"SELECT * FROM cached_tracks " +
|
||||
"WHERE albumId = :albumId ORDER BY discNumber ASC, trackNumber ASC",
|
||||
)
|
||||
fun observeByAlbum(albumId: String): Flow<List<CachedTrackEntity>>
|
||||
|
||||
@Query("SELECT * FROM cached_tracks WHERE id = :id")
|
||||
suspend fun getById(id: String): CachedTrackEntity?
|
||||
|
||||
@Query("SELECT * FROM cached_tracks WHERE id IN (:ids)")
|
||||
suspend fun getByIds(ids: List<String>): List<CachedTrackEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertAll(rows: List<CachedTrackEntity>)
|
||||
|
||||
@Query("DELETE FROM cached_tracks WHERE id IN (:ids)")
|
||||
suspend fun deleteByIds(ids: List<String>)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Cache row for one album. Mirrors `flutter_client/lib/cache/db.dart`'s
|
||||
* `CachedAlbums` Drift table.
|
||||
*/
|
||||
@Entity(tableName = "cached_albums")
|
||||
data class CachedAlbumEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val artistId: String,
|
||||
val title: String,
|
||||
val sortTitle: String,
|
||||
val releaseDate: String? = null,
|
||||
val coverPath: String? = null,
|
||||
val mbid: String? = null,
|
||||
val fetchedAt: Instant = Clock.System.now(),
|
||||
)
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Cache row for one artist. Mirrors `flutter_client/lib/cache/db.dart`'s
|
||||
* `CachedArtists` Drift table.
|
||||
*
|
||||
* Column names follow Kotlin idiom (camelCase) rather than Drift's
|
||||
* snake_case — the schema is purely internal to the native client; the
|
||||
* sync controller exchanges snake_case JSON with the server and our
|
||||
* mappers translate at the boundary.
|
||||
*/
|
||||
@Entity(tableName = "cached_artists")
|
||||
data class CachedArtistEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val name: String,
|
||||
val sortName: String,
|
||||
val mbid: String? = null,
|
||||
val artistThumbPath: String? = null,
|
||||
val artistFanartPath: String? = null,
|
||||
val fetchedAt: Instant = Clock.System.now(),
|
||||
)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.fabledsword.minstrel.cache.db.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Cache row for one track. Mirrors `flutter_client/lib/cache/db.dart`'s
|
||||
* `CachedTracks` Drift table.
|
||||
*/
|
||||
@Entity(tableName = "cached_tracks")
|
||||
data class CachedTrackEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val albumId: String,
|
||||
val artistId: String,
|
||||
val title: String,
|
||||
val durationMs: Int = 0,
|
||||
val trackNumber: Int? = null,
|
||||
val discNumber: Int? = null,
|
||||
val filePath: String? = null,
|
||||
val fileFormat: String? = null,
|
||||
val genre: String? = null,
|
||||
val fetchedAt: Instant = Clock.System.now(),
|
||||
)
|
||||
Reference in New Issue
Block a user