Merge pull request #2027 from nichwall/download-clear-invalid-queue

Download clear invalid queue
This commit is contained in:
advplyr authored and GitHub committed 2026-09-27 17:06:02 -05:00
commit 679ebb16f8
2 files changed
+95 -3

No files matched your search

@@ -121,6 +121,86 @@ class DbManager {
return downloadItems return downloadItems
} }
/**
* Validate persisted download queue entries before restoring them.
*
* Queue entries can become incompatible or malformed, for example after an app update. If any
* entry cannot be read or fails the checks required for queue restoration, the entire persisted
* queue is cleared so startup can continue without restoring invalid data.
*
* @return true if the queue passes validation or was successfully cleared; false if an invalid
* queue could not be cleared
*/
fun ensureValidDownloadQueue(): Boolean {
val downloadBook = Paper.book("downloadItems")
val queueIsValid =
try {
downloadBook.allKeys
.map { downloadItemId ->
val item =
downloadBook.read<DownloadItem>(downloadItemId)
?: throw IllegalStateException(
"Download queue entry $downloadItemId could not be read"
)
if (item.id != downloadItemId) {
throw IllegalStateException(
"Download queue entry $downloadItemId has a mismatched item ID"
)
}
item
}
.all { item ->
listOf<String?>(item.id, item.libraryItemId, item.localFolder.id).all {
!it.isNullOrBlank()
} &&
item.downloadItemParts.isNotEmpty() &&
item.downloadItemParts.all { part ->
listOf<String?>(
part.id,
part.downloadItemId,
part.filename,
part.destinationPath,
part.finalDestinationPath,
part.serverPath,
part.localFolderId
)
.all { !it.isNullOrBlank() } &&
part.downloadItemId == item.id
}
}
} catch (readError: Exception) {
AbsLogger.error(
tag,
"Could not read persisted download queue; clearing queue (${readError.message})"
)
false
}
if (queueIsValid) return true
AbsLogger.error(tag, "Persisted download queue contains invalid data; clearing queue")
return try {
downloadBook.destroy()
val remainingKeys = downloadBook.allKeys
if (remainingKeys.isEmpty()) {
AbsLogger.info(tag, "Cleared invalid persisted download queue")
true
} else {
AbsLogger.error(
tag,
"Invalid persisted download queue still contains ${remainingKeys.size} entries after clearing"
)
false
}
} catch (clearError: Exception) {
AbsLogger.error(
tag,
"Could not clear invalid persisted download queue (${clearError.message})"
)
false
}
}
fun saveLocalMediaProgress(mediaProgress: LocalMediaProgress) { fun saveLocalMediaProgress(mediaProgress: LocalMediaProgress) {
Paper.book("localMediaProgress").write(mediaProgress.id, mediaProgress) Paper.book("localMediaProgress").write(mediaProgress.id, mediaProgress)
} }
@@ -2,6 +2,7 @@ package com.audiobookshelf.app.services
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.device.FolderScanner import com.audiobookshelf.app.device.FolderScanner
import com.audiobookshelf.app.managers.DbManager import com.audiobookshelf.app.managers.DbManager
import com.audiobookshelf.app.managers.DownloadItemManager import com.audiobookshelf.app.managers.DownloadItemManager
@@ -43,9 +44,20 @@ object DownloadServiceHost {
DbManager.initialize(appContext) DbManager.initialize(appContext)
manager = DownloadItemManager(FolderScanner(appContext), appContext, ForwardingEmitter) manager = DownloadItemManager(FolderScanner(appContext), appContext, ForwardingEmitter)
restoreJob = scope.launch { restoreJob = scope.launch {
IncompleteDownloadCleanup.cleanupExpired(appContext) try {
manager!!.restoreQueue() if (DeviceManager.dbManager.ensureValidDownloadQueue()) {
onRestoreComplete(appContext) IncompleteDownloadCleanup.cleanupExpired(appContext)
manager!!.restoreQueue()
} else {
AbsLogger.error(TAG, "Skipping invalid download queue because it could not be cleared")
}
onRestoreComplete(appContext)
} catch (restoreError: Exception) {
AbsLogger.error(
TAG,
"Could not restore download queue (${restoreError.javaClass.simpleName}: ${restoreError.message})"
)
}
} }
} }
return manager!! return manager!!