fix(download): prune stale entries from the persisted queue on restore#231
fix(download): prune stale entries from the persisted queue on restore#231anishgoyal1108 wants to merge 1 commit into
Conversation
|
Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges. 📝 WalkthroughWalkthroughThis PR strengthens download queue integrity by detecting placeholder-string content corruption, validating the persistent queue on startup, reconciling legacy and current download progress tracking, and adding UI-level guards to prevent queuing already-downloaded chapters. ChangesDownload Queue Lifecycle and Placeholder Content Cleanup
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
DownloadManager restores its queue from JSON on startup but never pruned entries the legacy runDownloadService path had already fulfilled (that path writes content to the DB and removes DB rows without telling DownloadManager). Orphans accumulated and kept the "Downloading..." notification stuck at zero progress. On restore we now cross-check each queued entry against the DB and drop ones whose chapter no longer exists or already has content. Includes the chapterMapper / ChapterRepository content-length helpers the prune relies on and BookDetailViewModel download-state wiring.
eff5255 to
71c8db0
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
data/src/commonMain/kotlin/ireader/data/chapter/ChapterRepositoryImpl.kt (1)
142-146:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNotification uses wrong content check.
The notification checks
chapter.content.isNotEmpty()instead ofcontentForInsert.isNotEmpty(). When incoming content is the placeholder marker, it will be stripped to empty but the originalchapter.contentremains non-empty, triggering aContentFetchednotification even though no new content was written (the SQL COALESCE preserves existing DB content).🐛 Proposed fix
// Notify observers that chapter content was updated // This allows the reader screen to reload content when chapters are downloaded // by the downloader or other background processes - if (chapterNotifier != null && chapter.content.isNotEmpty()) { + if (chapterNotifier != null && contentForInsert.isNotEmpty()) { chapterNotifier.tryNotifyChange( ChapterNotifier.ChangeType.ContentFetched(chapter.id, chapter.bookId) ) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@data/src/commonMain/kotlin/ireader/data/chapter/ChapterRepositoryImpl.kt` around lines 142 - 146, The notification currently checks chapter.content.isNotEmpty() which can be non-empty even when no new content was written; change the condition that triggers chapterNotifier.tryNotifyChange(ChapterNotifier.ChangeType.ContentFetched(chapter.id, chapter.bookId)) to test contentForInsert.isNotEmpty() instead so notifications only fire when the incoming content actually contains non-placeholder data.
🧹 Nitpick comments (1)
data/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.kt (1)
88-88: ⚡ Quick winUse imported type alias for consistency.
The function signature uses the fully qualified type name
ireader.core.source.model.Pagedespite importingPageon line 4. The rest of the file uses the short form (e.g., line 18).♻️ Simplify type reference
-fun List<ireader.core.source.model.Page>.stripDownloadedPlaceholder(): List<ireader.core.source.model.Page> { +fun List<Page>.stripDownloadedPlaceholder(): List<Page> {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@data/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.kt` at line 88, The extension function signature uses the fully-qualified type name instead of the imported alias; update the signature of stripDownloadedPlaceholder (the function declared as fun List<ireader.core.source.model.Page>.stripDownloadedPlaceholder()) to use the imported Page type (fun List<Page>.stripDownloadedPlaceholder()) so it matches the rest of the file's short-form references and maintains consistency with other usages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@data/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.kt`:
- Around line 90-92: Replace the hardcoded marker string in the onlyPlaceholder
check with the shared constant (e.g., DOWNLOADED_CHAPTER_PLACEHOLDER) or a new
private constant used by both stripDownloadedPlaceholder and Chapter.isEmpty;
update the expression in chapterMapper.kt where onlyPlaceholder is computed (the
lambda checking page.text.contains(...)) to reference that constant instead of
the literal so both locations share the same marker.
---
Outside diff comments:
In `@data/src/commonMain/kotlin/ireader/data/chapter/ChapterRepositoryImpl.kt`:
- Around line 142-146: The notification currently checks
chapter.content.isNotEmpty() which can be non-empty even when no new content was
written; change the condition that triggers
chapterNotifier.tryNotifyChange(ChapterNotifier.ChangeType.ContentFetched(chapter.id,
chapter.bookId)) to test contentForInsert.isNotEmpty() instead so notifications
only fire when the incoming content actually contains non-placeholder data.
---
Nitpick comments:
In `@data/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.kt`:
- Line 88: The extension function signature uses the fully-qualified type name
instead of the imported alias; update the signature of
stripDownloadedPlaceholder (the function declared as fun
List<ireader.core.source.model.Page>.stripDownloadedPlaceholder()) to use the
imported Page type (fun List<Page>.stripDownloadedPlaceholder()) so it matches
the rest of the file's short-form references and maintains consistency with
other usages.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 67bb2527-301f-4638-ab87-2dd8e0bbd65e
📒 Files selected for processing (6)
data/src/commonMain/kotlin/ireader/data/chapter/ChapterRepositoryImpl.ktdata/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.ktdata/src/commonMain/kotlin/ireader/data/repository/consolidated/ChapterRepositoryImpl.ktdomain/src/commonMain/kotlin/ireader/domain/services/download/DownloadManager.ktdomain/src/desktopMain/kotlin/ireader/domain/services/common/DesktopDownloadService.ktpresentation/src/commonMain/kotlin/ireader/presentation/ui/book/viewmodel/BookDetailViewModel.kt
| val onlyPlaceholder = all { page -> | ||
| page is ireader.core.source.model.Text && page.text.contains("PLACEHOLDER_DO_NOT_DISPLAY_THIS_TEXT_TO_USER") | ||
| } |
There was a problem hiding this comment.
Hardcoded substring creates maintenance risk.
The placeholder detection uses a hardcoded substring "PLACEHOLDER_DO_NOT_DISPLAY_THIS_TEXT_TO_USER" instead of referencing DOWNLOADED_CHAPTER_PLACEHOLDER or extracting the shared substring. The same substring appears in Chapter.isEmpty(), suggesting duplication. If the constant changes, both locations must be updated manually.
🔧 Proposed fix using the constant
val onlyPlaceholder = all { page ->
- page is ireader.core.source.model.Text && page.text.contains("PLACEHOLDER_DO_NOT_DISPLAY_THIS_TEXT_TO_USER")
+ page is ireader.core.source.model.Text && page.text.contains(DOWNLOADED_CHAPTER_PLACEHOLDER)
}Alternatively, extract the substring as a shared constant:
private const val PLACEHOLDER_MARKER = "PLACEHOLDER_DO_NOT_DISPLAY_THIS_TEXT_TO_USER"then reference it in both stripDownloadedPlaceholder() and Chapter.isEmpty().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data/src/commonMain/kotlin/ireader/data/chapter/chapterMapper.kt` around
lines 90 - 92, Replace the hardcoded marker string in the onlyPlaceholder check
with the shared constant (e.g., DOWNLOADED_CHAPTER_PLACEHOLDER) or a new private
constant used by both stripDownloadedPlaceholder and Chapter.isEmpty; update the
expression in chapterMapper.kt where onlyPlaceholder is computed (the lambda
checking page.text.contains(...)) to reference that constant instead of the
literal so both locations share the same marker.
Description
DownloadManagerrestores its queue from JSON on startup but never pruned entriesthe legacy
runDownloadServicepath had already fulfilled — that path writes chaptercontent to the DB and removes the DB download rows on completion without telling
DownloadManagerto drop the matching persisted-queue entries. Over time the JSONqueue accumulated orphans that kept the "Downloading…" notification stuck at zero
real progress.
On restore we now cross-check each queued entry against the DB and drop ones whose
chapter no longer exists (book deleted) or that already have content. Includes the
chapterMapper/ChapterRepositorycontent-length helpers the prune relies on andthe related
BookDetailViewModeldownload-state wiring.Type of Change
Testing
notification no longer sticks at 0%
Additional Notes
Applied cleanly onto current
master. Desktop-focused but the prune logic lives incommonMain.Summary by CodeRabbit
Bug Fixes
Improvements