library: fix credited fallback and stale marker writes

Restore the both-tables-empty fallback in the credit recompute, skip
redundant marker writes, append albumless tracks after album tracks, and make
the paged artist query observe sources. Add tests for an interrupted sync and
the partial-relation migration guard.
This commit is contained in:
austinried
2026-10-18 07:00:04 +00:00
parent 9d9550be48
commit a41f77d907
6 changed files with 85 additions and 7 deletions
@@ -18,6 +18,7 @@ import com.subtracks.data.model.Playlist
import com.subtracks.data.model.PlaylistSong
import com.subtracks.data.model.PlaylistSongItem
import com.subtracks.data.model.Song
import com.subtracks.data.model.Source
import kotlinx.coroutines.flow.Flow
internal const val PLAYLIST_SONGS_SELECT =
@@ -250,7 +251,7 @@ interface LibraryDao {
"UPDATE artists SET songCount = c.songCount, credited = c.credited " +
"FROM (SELECT artists.id AS id, COALESCE(counts.songCount, 0) AS songCount, " +
"((NOT EXISTS (SELECT 1 FROM album_artists WHERE sourceId = artists.sourceId) " +
"OR NOT EXISTS (SELECT 1 FROM song_artists WHERE sourceId = artists.sourceId)) " +
"AND NOT EXISTS (SELECT 1 FROM song_artists WHERE sourceId = artists.sourceId)) " +
"OR EXISTS (SELECT 1 FROM album_artists aa " +
"WHERE aa.sourceId = artists.sourceId AND aa.artistId = artists.id) " +
"OR EXISTS (SELECT 1 FROM song_artists sa " +
@@ -567,7 +568,7 @@ interface LibraryDao {
@RawQuery(observedEntities = [Album::class])
fun albumsPaging(query: RoomRawQuery): PagingSource<Int, Album>
@RawQuery(observedEntities = [Artist::class])
@RawQuery(observedEntities = [Artist::class, Source::class])
fun artistsPaging(query: RoomRawQuery): PagingSource<Int, Artist>
@RawQuery(observedEntities = [Playlist::class])
@@ -38,7 +38,7 @@ private const val ARTIST_SONGS_SELECT =
"WHERE songs.sourceId = :sourceId AND songs.id IN $ARTIST_SONG_IDS"
private const val ARTIST_SONGS_ORDER =
" ORDER BY albums.year DESC, albums.sortName COLLATE NOCASE, albums.id, songs.disc, songs.track, songs.id"
" ORDER BY albums.id IS NULL, albums.year DESC, albums.sortName COLLATE NOCASE, albums.id, songs.disc, songs.track, songs.id"
private const val SONG_SQL =
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
@@ -32,12 +32,19 @@ interface SourcesDao {
@Query("SELECT creditsStale FROM sources WHERE id = :sourceId")
suspend fun creditsStale(sourceId: Long): Boolean?
@Query("UPDATE sources SET creditsStale = :stale WHERE id = :sourceId")
@Query("UPDATE sources SET creditsStale = :stale WHERE id = :sourceId AND creditsStale != :stale")
suspend fun setCreditsStale(
sourceId: Long,
stale: Boolean,
)
@Query("UPDATE sources SET name = :name, address = :address WHERE id = :sourceId")
suspend fun updateSourceDetails(
sourceId: Long,
name: String,
address: String,
)
@Insert
suspend fun insertSource(source: Source): Long
@@ -220,8 +220,8 @@ class SourceRepository(
useTokenAuth: Boolean,
) {
val dao = db.sourcesDao()
val source = dao.sourceOnce(id) ?: return
dao.upsertSource(source.copy(name = name.trim(), address = normalizeAddress(address)))
if (dao.sourceOnce(id) == null) return
dao.updateSourceDetails(id, name.trim(), normalizeAddress(address))
dao.upsertSubsonicSource(
SubsonicSource(
sourceId = id,