Skip to content

feat: auto-download after sync, master stop, and batch downloads#34

Merged
Promises merged 3 commits into
Promises:mainfrom
kvmgithub:feat/auto-download-stop-all-batch
Jul 19, 2026
Merged

feat: auto-download after sync, master stop, and batch downloads#34
Promises merged 3 commits into
Promises:mainfrom
kvmgithub:feat/auto-download-stop-all-batch

Conversation

@kvmgithub

Copy link
Copy Markdown
Contributor

Summary

Auto-download existed in the codebase but had never worked: enabling it could crash, nothing ever emitted the sync event it waits for, a relaunch left no listener armed, its WiFi gate read a flag nothing sets, and its book scan read fields the query doesn't return — so it never downloaded anything. It also ran on a second download engine that could only be stopped during the download stage, which is how stuck, uncancelable tasks appeared. This makes auto-download work end to end on the same pipeline as manual downloads, adds a master "Stop All" control with two scopes, and adds a batch multi-select download mode to the library. Builds on #33.

What changed

  • Auto-download triggers after a completed library sync — both the in-app page-by-page sync and the scheduled WorkManager sync now notify the task manager; nothing emitted this event before. Enabling only arms the listener (no immediate batch of downloads), replayed events from before arming are ignored, and the listener is re-armed on cold start when the setting was left on.
  • The auto-download book scan uses the filtered query that actually returns file_path/audible_product_id/authors, picks downloadable books without a saved file, and enqueues through the same DownloadService/DownloadOrchestrator as manual downloads — same per-stage progress, cancel, cleanup, per-book notifications, and duplicate-enqueue protection (also enforced in the service itself).
  • Auto-download is exposed as a normal setting (Settings > Library Sync > "Auto-Download New Books", default off). The debug screen keeps its controls and now reflects the real persisted state.
  • The WiFi-only gate queries connectivity live; the flag it used to read was only ever set by a service that no longer runs.
  • The background worker pipeline gained the foreground pipeline's mid-stage cancel: per-book FFmpeg session cancel, cooperative abort in validation and copy, partial-file and empty-folder cleanup, and tasks end CANCELLED instead of stuck. Clearing tasks stops the monitoring loops too, so cleared tasks stop polling.
  • New "Stop All Downloads" button in Settings asks for scope: cancel all downloads (active, queued, paused), or additionally stop background task processing and all scheduled work. Each Rust cancel result is verified and the reported count is honest.
  • Stopping never un-downloads a book: completed download rows are preserved because they link a downloaded book to its file — cancelling them dropped books back to "not downloaded" until the next sync.
  • Decrypt guards against an empty or missing downloaded file and resets the task for a fresh download instead of failing with a misleading "activation bytes may be wrong" error. Cancelling a download is logged as a cancellation, not an error.
  • Library batch mode: a toolbar toggle enters multi-select, tapping selects downloadable books, "Download (N)" starts them all and reports how many actually started.
  • The audio validation routine is shared by both pipelines (one implementation instead of two drifting copies), and a cancelled validation propagates as a cancel rather than a corrupt-file verdict.

User impact

Turning on auto-download (now a normal setting, off by default) downloads new, not-yet-downloaded books after each library sync over WiFi. Auto-downloads behave exactly like manual ones: visible progress for every stage, cancelable mid-stage, no leftovers on cancel. One button stops everything — downloads only, or every running process — without breaking the "downloaded" state of finished books, and without needing a re-sync afterwards. Multiple books can be selected and downloaded in one go from the library.

Implementation notes

The second download engine (background DownloadWorker) is still used for restored tasks, so it received the same cancel/cleanup mechanics as the orchestrator rather than being removed in this change; new auto-downloads no longer use it and route through the foreground service. The sync-complete event carries an emission timestamp so the listener can drop events replayed by the shared flow's buffer — otherwise enabling the toggle right after a sync would immediately start downloads, which the listener-only design is meant to avoid. Master stop only cancels rows in cancelable states; a completed row doubles as the downloaded-book file link, which is why it must survive.

Validation

Automated validation:

  • npm run typecheck

Manual Android validation:

  • Enable auto-download, run a library sync: only not-yet-downloaded books are enqueued; already-downloaded and in-flight books are skipped.
  • Enable auto-download with no sync: nothing starts until the next sync completes.
  • Relaunch with auto-download enabled, sync: downloads trigger (listener re-armed).
  • Cancel an auto-download during decrypt/validation/copy from the notification or library: stops promptly, partial files removed, book re-downloadable.
  • Stop All > cancel all downloads: active/queued/paused downloads stop, downloaded books stay downloaded, no re-sync needed; scheduled sync keeps running.
  • Stop All > cancel all running processes: additionally clears background tasks and scheduled work; debug task list is empty afterwards.
  • Batch mode: select several books, download; summary reports how many started; selection of downloaded/active books is prevented.

The notification Cancel button only appeared while downloading, and neither the
notification nor the in-app cancel could interrupt the post-download stages: a
decrypt or a multi-GB SAF copy of a large audiobook ran to completion even after
the user cancelled.

Cancel is now offered in every stage, in the notification and the library list.
The orchestrator tracks the running FFmpeg session per book and, on cancel,
cancels that specific session (so parallel conversions are unaffected) and sets
a flag that the buffered copy and the validation loop check, aborting promptly.
Decrypt uses executeAsync so its session id is available to cancel. The cancel
broadcast now always stops monitoring (which performs the abort) rather than
only when the Rust download-cancel succeeds — past the download stage that call
fails, which previously left the copy running. Cancelling mid-copy also deletes
the partial output file and any folders created for it, so a later library scan
cannot find and relink it.

A book counts as downloaded only once its final file is saved, not when the
download task first reports "completed" (that status is also set when only the
download, not the whole pipeline, finished). Cancelled, failed and interrupted
books stay retriable: the library shows the download button again and clears the
stale task before re-enqueuing. Retry works again — it accepts the ".aaxc"
encrypted cache (not only legacy ".aax"), and the error notification gained a
real Retry action instead of an inert "tap to retry" hint.
Auto-download existed but never worked: enabling it could crash on an
uninitialized worker, no code ever emitted the sync-complete event it listens
for, the listener was armed only from the UI toggle so a relaunch left nothing
listening, the WiFi gate read a flag no running code ever set, and the book scan
read fields the query never returned — so nothing was ever enqueued. It also ran
on a second download engine with none of the cancel/cleanup behavior of the
foreground pipeline.

Auto-download now works end to end and runs on the same pipeline as manual
downloads: a completed library sync (from the in-app sync or the scheduled
WorkManager sync) triggers a check that picks downloadable books without a saved
file and enqueues them through DownloadService, giving them the same per-stage
progress, cancel, cleanup, notifications and per-book dedup. Enabling the
toggle — now also available in Settings > Library Sync, default off — only arms
the listener; downloads start after the next sync, and replayed events from
before arming are ignored. The WiFi-only gate queries connectivity live.

The background worker pipeline gained the same mid-stage cancel as the
foreground one (per-book FFmpeg session cancel, cooperative validation/copy
abort, partial-file and folder cleanup, tasks end cancelled instead of stuck),
and clearing tasks stops monitoring loops instead of leaving them polling.

New master "Stop All Downloads" control in Settings with two scopes: cancel all
downloads (active, queued, paused — the foreground engine is torn down and each
Rust cancel is verified and counted) or additionally stop background task
processing and scheduled work. Completed download rows are never touched: they
link downloaded books to their files, so stopping never un-downloads a book and
needs no re-sync to recover. Guarded against decrypting an empty or missing
download (previously surfaced as a misleading activation-bytes error), and
download cancellation is no longer logged as an error.

The library gained a batch mode: a toolbar toggle switches to multi-select,
tapping selects downloadable books, and one action downloads the selection with
an honest started-count summary. The audio validation routine is shared by both
pipelines instead of duplicated, and a cancelled validation propagates as a
cancel rather than a corrupt-file verdict.
@Promises

Copy link
Copy Markdown
Owner

Thank you, I am still traveling, I will see if I can make some time at a hotel and review it.

@Promises
Promises merged commit 965166c into Promises:main Jul 19, 2026
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.

2 participants