address review: count a play even when its scrobble fails

A submitted scrobble now counts locally regardless of the HTTP result
(offline already did), so a transient server error no longer drops the
play from the sorts. Last-played uses MAX(existing, at) so a backwards
clock step cannot move an album or artist back down the list.
This commit is contained in:
austinried
2026-10-04 00:30:05 +00:00
parent 95ac4ae6ce
commit 4d4eb7b758
5 changed files with 64 additions and 15 deletions
@@ -179,14 +179,20 @@ interface LibraryDao {
recomputeArtistPlayData(sourceId)
}
@Query("UPDATE songs SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId AND id = :songId")
@Query(
"UPDATE songs SET playCount = playCount + 1, played = MAX(COALESCE(played, :at), :at) " +
"WHERE sourceId = :sourceId AND id = :songId",
)
suspend fun bumpSongPlay(
sourceId: Long,
songId: String,
at: Long,
)
@Query("UPDATE albums SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId AND id = :albumId")
@Query(
"UPDATE albums SET playCount = playCount + 1, played = MAX(COALESCE(played, :at), :at) " +
"WHERE sourceId = :sourceId AND id = :albumId",
)
suspend fun bumpAlbumPlay(
sourceId: Long,
albumId: String,
@@ -194,8 +200,8 @@ interface LibraryDao {
)
@Query(
"UPDATE artists SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId " +
"AND id = (SELECT artistId FROM albums WHERE sourceId = :sourceId AND id = :albumId)",
"UPDATE artists SET playCount = playCount + 1, played = MAX(COALESCE(played, :at), :at) " +
"WHERE sourceId = :sourceId AND id = (SELECT artistId FROM albums WHERE sourceId = :sourceId AND id = :albumId)",
)
suspend fun bumpArtistPlay(
sourceId: Long,
@@ -126,17 +126,21 @@ class Scrobbler(
}
private suspend fun submit(event: Scrobble) {
try {
when (event) {
is Scrobble.NowPlaying -> {
sink.nowPlaying(event.songId)
}
is Scrobble.Submission -> {
sink.scrobble(event.songId, event.time)
recordPlay(event.songId, event.time / 1000)
}
when (event) {
is Scrobble.NowPlaying -> {
attempt { sink.nowPlaying(event.songId) }
}
is Scrobble.Submission -> {
attempt { recordPlay(event.songId, event.time / 1000) }
attempt { sink.scrobble(event.songId, event.time) }
}
}
}
private suspend fun attempt(block: suspend () -> Unit) {
try {
block()
} catch (cancellation: CancellationException) {
throw cancellation
} catch (_: Exception) {