fix: AudiobookShelf push sends lastUpdate; 60 s per-level fetch throttle - #132
Conversation
The push sent position and finished state only, so ABS stamped each entry with the moment the push landed rather than when the position was reached; a push delayed by the queue or offline listening recorded a later time than the play it carried. The push now sends `lastUpdate`, the save's lastPlayDate in epoch ms — the same date the Jellyfin push sends as LastPlayedDate since #130. Checked against AudiobookShelf 2.37: an update to an existing entry stores the client `lastUpdate` exactly; the first write for a book (which creates the entry) still takes the server's clock. A task queued before the date was recorded omits the field, and ABS stamps its own time as before.
The per-level contents fetch was throttled at 30 s in two places over the same timestamp map (canFetchContents, used by the library screen, and checkAndMarkFetchContents, used when the fetch task is created). Both now use one 60 s constant, matching iOS's per-level list sync. The sort preferences pull, documented as sharing the contents fetch's cadence, moves with it; the account-wide identifiers sync keeps its own 30 s throttle. A clock seam and a reset make the throttle testable.
✅ Claude PR Review —
|
| Finding | Status |
|---|---|
core/src/main/java/com/tortugapower/audiobookplayer/logic/SyncStatusManager.kt:33 (info) |
✅ verified fixed in a93c89c |
Converged: nothing new this round, and every earlier finding is settled.
Model claude-opus-5-5 · run log · 0 new · 0 carried over · 1 verified closed · 0 resolved · advisory (a human should still review). Findings are de-duplicated across pushes; an earlier finding closes only when the verification pass judges it against the current code — fixed, no longer applicable, accepted by a maintainer, or a duplicate of a finding reported on this push.
Three comments still described the 30 s throttle the previous commit replaced.
SyncStatusManager's throttle test seams (clock, resetFetchThrottles) are marked @VisibleForTesting so lint flags production use, and clock is @volatile since the object is process-wide.
Two small follow-ups from the media-server progress work (#129, #130).
Change
fix: the AudiobookShelf progress push sends its play date as lastUpdate— the push sent position and finished state only, so ABS stamped each entry with the moment the push landed, not when the position was reached (later than the real play for a push delayed by the queue or offline listening).AudiobookshelfProgressRequestgainslastUpdate(epoch ms), filled from the task payload'slastPlayDate— the same date the Jellyfin push sends asLastPlayedDatesince fix: Jellyfin progress push reaches the server, with its play date #130. A task queued before the date was recorded omits it (Gson skips nulls) and ABS stamps its own time, as before.fix: throttle each library level's contents fetch to once per 60 seconds— the per-level fetch was throttled at 30 s in two places over the same timestamp map (canFetchContentsfromLibraryScreen,checkAndMarkFetchContentsincreateFetchContentsTask). Both use one 60 s constant now, matching iOS's per-level list sync. The sort-preferences pull, documented as sharing the contents fetch's cadence, moves with it; the account-wide identifiers sync keeps its separate 30 s throttle.Verification
AudiobookShelf 2.37 (throwaway container), emulator bp-api36, a book downloaded from that server:
lastPlayDatelastUpdateTests.
./gradlew assembleDevDebug testDevDebugUnitTest :core:testDebugUnitTest lintDevDebuggreen (746 tests: 217 app, 68 wear, 461 core). New: the ABS push carrieslastUpdatefrom the payload and omits it for a dateless task (ExternalUpdateProcessorTest); a level can't refetch within 60 s (past the old 30 s), the throttle is per level, check-and-mark and the preferences pull share the 60 s cadence (SyncStatusManagerThrottleTest, with a clock seam and a reset). Setting the throttle back to 30 s or droppinglastUpdatefails exactly those tests.Found along the way (not in this PR)
.mp3. ABS'sapi/items/{id}/downloadreturns a zip for a book in its own folder; the download is namedoriginalFileName ?: "<title>.mp3"(ABS list items are minified, so it's always the fallback) and archive expansion goes by that name, so it's never unzipped. iOS names downloads fromContent-Disposition("Book.zip") instead. Separate PR.server-settings, the constant ABS reports asserverSettings.id, so two ABS servers can't be told apart by host id.