Skip to content

fix: AudiobookShelf push sends lastUpdate; 60 s per-level fetch throttle - #132

Merged
GianniCarlo merged 4 commits into
developfrom
fix/abs-lastupdate-fetch-throttle
Sep 30, 2026
Merged

GianniCarlo merged 4 commits into
developfrom
fix/abs-lastupdate-fetch-throttle

Conversation

@GianniCarlo

Copy link
Copy Markdown
Contributor

Two small follow-ups from the media-server progress work (#129, #130).

Change

  1. 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). AudiobookshelfProgressRequest gains lastUpdate (epoch ms), filled from the task payload's lastPlayDate — the same date the Jellyfin push sends as LastPlayedDate since 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.
  2. 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 (canFetchContents from LibraryScreen, checkAndMarkFetchContents in createFetchContentsTask). 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:

Push local lastPlayDate ABS lastUpdate
Play start — first write, creates the entry 1790768212663 1790768213679 (ABS's clock: the first write ignores a client value)
Pause — updates the entry 1790768224474 1790768224474 (exact)

Tests. ./gradlew assembleDevDebug testDevDebugUnitTest :core:testDebugUnitTest lintDevDebug green (746 tests: 217 app, 68 wear, 461 core). New: the ABS push carries lastUpdate from 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 dropping lastUpdate fails exactly those tests.

Found along the way (not in this PR)

  • ABS downloads land as an unplayable zip named .mp3. ABS's api/items/{id}/download returns a zip for a book in its own folder; the download is named originalFileName ?: "<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 from Content-Disposition ("Book.zip") instead. Separate PR.
  • Every ABS server gets the stable id server-settings, the constant ABS reports as serverSettings.id, so two ABS servers can't be told apart by host id.

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.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

✅ Claude PR Review — PASS

This PR does two things.

  • AudiobookShelf push: the progress push now sends lastUpdate in epoch ms, taken from the task payload's lastPlayDate. That is the same millisecond value the Jellyfin push already turns into LastPlayedDate. The field is nullable and defaults to null, and the :core Gson setup doesn't enable serializeNulls, so tasks queued before the date was recorded still leave the field out.
  • Fetch throttle: the per-level contents fetch and the preferences pull now share one 60 s FETCH_THROTTLE_MS constant. A clock hook was added for tests. The account-wide identifiers throttle stays at 30 s on purpose.

I checked the callers and the payload producer (SyncTaskFactory stores lastPlayDate as epoch ms). Both new test files cover the behaviour and put SyncStatusManager's global state back afterwards. Open finding #1 looks fixed (@VisibleForTesting and @Volatile are now applied). I found no new problems.

Findings: no findings

Previously raised

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.
@GianniCarlo
GianniCarlo merged commit 6539aa3 into develop Sep 30, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
reviewer — a93c89c0 Deployed Sep 30, 2026 by GianniCarlo via review #454
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant