Harden Android sync and download flows#10
Merged
Promises merged 1 commit intoMay 10, 2026
Conversation
Use shared app database paths across JS, Kotlin, and workers so sync, account, and download state stay in one SQLite store. Move SAF deletion into the Android bridge so downloaded books, optional Smart Audiobook Player covers, and empty per-book folders are cleaned up without deleting unrelated files.
Owner
|
Thank you, will do a check and test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
This PR hardens the Android sync and download flows around a concrete user-facing cleanup problem: when a downloaded audiobook is marked as not downloaded and the user chooses to delete the file, LibriSync should also clean up the files and folders that LibriSync created for that specific book. That includes the Smart Audiobook Player sidecar cover (
EmbeddedCover.jpg), an empty per-book folder, and an empty author folder. The cleanup must stay conservative: it must not delete other books, other cover files that still belong to remaining audio files, or an author folder that still contains other content.While validating that path, this also fixes a related consistency issue in the Android app: different parts of the Android stack were using different database locations. Foreground React Native screens, foreground services, background workers, notification actions, and Rust JNI calls now use the same app database path so account state, library metadata, download tasks, and final file paths stay in one SQLite store.
What changed
Shared Android database path
The app now has a single database path convention for Android:
src/utils/appPaths.tsto deriveaudible.dbfrom the Expo app document/files directory.AppPaths.databasePath(context)for Android services, receivers, workers, and bridge code.DownloadService,DownloadActionReceiver,BackgroundTaskManager,DownloadWorker,LibrarySyncWorker, andTokenRefreshWorkernow use the same path helper instead of private cache-dir variants.This matters because the previous split could leave background workers looking at one database while the UI looked at another. That made downloads, account lookups, token refresh, sync state, and file-path lookup unreliable after app restarts or when background services were involved.
Download deletion and cleanup
clearBookDownloadState(..., deleteFile = true)now performs Android-aware deletion before clearing the Rust download state.For Android SAF/content URI downloads, the bridge now:
DocumentsContractor the persisted writable document tree;EmbeddedCover.jpgonly when no other audio file remains in that same book folder;The returned result now includes cleanup details such as:
file_deleteddeleted_pathcover_deletedbook_folder_deletedauthor_folder_deletedcleanup_errordelete_errorThe UI uses those fields to distinguish full success from partial cleanup. If the audiobook file was deleted but follow-up folder cleanup failed, the app reports that as partial cleanup instead of pretending everything was removed.
Conservative deletion rules
The cleanup is intentionally defensive:
EmbeddedCover.jpgfrom being deleted.This is the key safety behavior for users with multiple books by the same author or manually managed files in the download directory.
Smart Audiobook Player cover handling
The cover-art path is now handled in both directions:
EmbeddedCover.jpgnext to the final audiobook file when the Smart Audiobook Player compatibility setting is enabled.EmbeddedCover.jpgfiles are replaced cleanly when writing a new cover.This keeps Smart Audiobook Player compatibility while avoiding stale cover art after a book is removed.
Download task and final output path consistency
The Android conversion flow now writes the final output path back into the persistent Rust download task after the file is copied to the user-selected destination.
Changes include:
DownloadOrchestrator.copyToFinalDestination(...)now returns the actual final path.nativeUpdateDownloadTaskStatusaccepts an optionaloutput_path;PersistentDownloadManageraddsupdate_task_status_with_details(...)to update status, error, and output path in one place.This is important because deletion depends on the final stored path. Without this, the database could keep a stale cache path while the real audiobook lived in the SAF destination.
Account and sync flow hardening
This PR also tightens account handling for Android background flows:
This makes background sync, manual sync, token refresh, and download enqueueing operate against the same persisted account data.
Background service and manifest setup
The Android manifest/config plugin setup is expanded so generated native projects keep the required service declarations:
BackgroundTaskServiceis declared alongsideDownloadService.BootReceiveris declared for boot/package-replaced recovery hooks.RECEIVE_BOOT_COMPLETEDis added in app config and the Expo config plugin.This keeps
expo prebuildoutput aligned with the checked-in Android module manifest.Rust/API safety and test support
The Rust side includes supporting hardening:
Cargo.lockis checked in for reproducible Rust dependency resolution.User-visible behavior
After this change, when a user chooses Delete File from the library screen for a downloaded book:
EmbeddedCover.jpgis deleted if it belongs only to that now-deleted book folder.The app also handles partial cleanup more clearly. For example, if the audiobook file is deleted but Android refuses to remove a sidecar file or folder, the UI can report the cleanup issue without losing the database state reset.
Review focus
The main review areas are:
ExpoRustBridgeModule.kt, especially tree-permission lookup and parent-folder cleanup.Validation
Automated and build validation run for this branch:
npm run typecheckcargo testgit diff --checknpm run build:rust:android./gradlew :expo-rust-bridge:compileDebugKotlin./gradlew :app:assembleDebugManual Android validation was also performed for the download deletion flow, including SAF-backed output, Smart Audiobook Player cover cleanup, empty book-folder cleanup, and preserving author folders when other books remain.