Skip to content

Store download, delete and new actions without overwriting play positions - #244

Merged
thrillfall merged 2 commits into
thrillfall:mainfrom
arikisonfire:store-download-delete-actions
Oct 30, 2026
Merged

thrillfall merged 2 commits into
thrillfall:mainfrom
arikisonfire:store-download-delete-actions

Conversation

@arikisonfire

@arikisonfire arikisonfire commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Since 3.13.3 (531d6f4) the app drops every action except play, because the table kept a single row per episode and a download, delete or new action replaced the play position (#168, AntennaPod/AntennaPod#6957).

This keeps one row per episode and action type instead. The unique indexes become (episode, action, user_id) and (guid, action, user_id), and the lookups that decide between insert and update include the action. A download or delete action can no longer touch the play position, so the play filter on upload is removed. Lookups called without an action still return the play row.

GET episode_action still returns only play actions, unless a client asks for the others with allActions=true. Existing clients keep seeing what they see today. One exception: rows of another action type stored before 3.13.3 no longer appear in the default list.

Why: clients can see what other devices downloaded, for example a desktop app that offers what AntennaPod downloaded on the phone.

Why opt-in: not every client checks the action type. YourPods keeps the newest row per episode whatever its type, and Podcast Merlin writes the position of every row to its database, so a delete row with position -1 would hide or overwrite the play position there. FocusPodcast applies new without comparing times. AntennaPod 3.12.3, Podcini.X, Kasts, Cardo and RePod act on play only. I read this in their sources and did not run them.

Testing:

  • The test suite plus three new tests (the play position stays when download, delete and new actions arrive, with and without guid, and the list returns the other actions only when asked): locally on Nextcloud 35 with PHP 8.5 and SQLite, and in CI on my fork for SQLite, MySQL and PostgreSQL with PHP 8.2–8.5 and Nextcloud 32–35.
  • Upgrade on a local Nextcloud 35 through the HTTP API: with 3.18.0 a play action was stored and a download dropped; after switching to this branch with the version raised to 3.18.1, the migration replaced the indexes, the play row survived, download and delete actions were stored, and a later play updated only the play row.
  • A desktop client against that upgraded instance: with allActions=true it listed an episode as downloaded elsewhere and kept its play position after a later delete. The phone's actions were sent to the HTTP API in AntennaPod's JSON format, not by AntennaPod on a real device.

The migration runs once the app version is raised; I left that for the release. Made with AI assistance (Claude Code), including the tests above.

🤖 Generated with Claude Code

@Xefir

Xefir commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Hello

Thank you for trying to resolve this annoying issue.
It'll work for RePod because I use the gPodder PHP function directly to query database : https://git.crystalyx.net/Xefir/repod/src/branch/main/lib/Controller/EpisodesController.php#L60
So yeah, good for me.

Thank you again and happy podcasting !

YourPods and Podcast Merlin do not check the action type of the rows they
receive, so download, delete and new rows with position -1 would hide or
overwrite the play position there. GET episode_action keeps returning only
play actions, and allActions=true also returns the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arikisonfire

Copy link
Copy Markdown
Contributor Author

Thanks @Xefir, that settles RePod. I also checked the list path: App.vue reads episode_action but filters on play, so that is fine too. One small effect: an episode whose only row is a DELETE from before 3.13.3 no longer counts as ended in RePod, because the lookups now return the play row only.

While checking that, I read the other clients in the README as well, and two of them don't look at the action type (details in the description). So I changed the approach: all action types are stored, but GET episode_action returns only play unless a client asks with allActions=true. Existing clients keep seeing what they see today.

@thrillfall
thrillfall merged commit 1f190b3 into thrillfall:main Oct 30, 2026
43 checks passed
@thrillfall

Copy link
Copy Markdown
Owner

thanks for you contribution!

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.

3 participants