Inline download resume policy

This commit is contained in:
Nicholas Wallace
2026-08-27 15:12:04 -07:00
parent c800ed727a
commit f25ff6344e
3 changed files with 13 additions and 79 deletions
@@ -1,23 +0,0 @@
package com.audiobookshelf.app.managers
internal object DownloadResumePolicy {
enum class InitialAction { COMPLETE, RESTART, FULL_DOWNLOAD, RANGE_DOWNLOAD }
fun initialAction(existingBytes: Long, expectedSize: Long): InitialAction =
when {
expectedSize > 0L && existingBytes == expectedSize -> InitialAction.COMPLETE
expectedSize > 0L && existingBytes > expectedSize -> InitialAction.RESTART
existingBytes > 0L -> InitialAction.RANGE_DOWNLOAD
else -> InitialAction.FULL_DOWNLOAD
}
fun unsatisfiedRangeSize(contentRange: String?): Long? {
if (contentRange == null) return null
return UNSATISFIED_CONTENT_RANGE.matchEntire(contentRange)
?.groupValues
?.get(1)
?.toLongOrNull()
}
private val UNSATISFIED_CONTENT_RANGE = Regex("bytes \\*/(\\d+)")
}
@@ -61,21 +61,18 @@ class InternalDownloadManager(
allowRestart: Boolean
) {
var existingBytes = destinationFile.takeIf { it.exists() }?.length() ?: 0L
when (DownloadResumePolicy.initialAction(existingBytes, expectedSize)) {
DownloadResumePolicy.InitialAction.COMPLETE -> {
progressCallback.onProgress(existingBytes, 100L)
progressCallback.onComplete(false)
if (expectedSize > 0L && existingBytes == expectedSize) {
progressCallback.onProgress(existingBytes, 100L)
progressCallback.onComplete(false)
return
}
if (expectedSize > 0L && existingBytes > expectedSize) {
if (!destinationFile.delete()) {
Log.e(tag, "Could not delete oversized staging file ${destinationFile.name}")
progressCallback.onComplete(true)
return
}
DownloadResumePolicy.InitialAction.RESTART -> {
if (!destinationFile.delete()) {
Log.e(tag, "Could not delete oversized staging file ${destinationFile.name}")
progressCallback.onComplete(true)
return
}
existingBytes = 0L
}
else -> Unit
existingBytes = 0L
}
val request =
Request.Builder()
@@ -98,8 +95,9 @@ class InternalDownloadManager(
try {
if (response.code == 416) {
val serverSize =
DownloadResumePolicy.unsatisfiedRangeSize(
response.header("Content-Range"))
response.header("Content-Range")
?.removePrefix("bytes */")
?.toLongOrNull()
if (serverSize != null && serverSize > 0L && existingBytes == serverSize) {
progressCallback.onProgress(existingBytes, 100L)
progressCallback.onComplete(false)
@@ -1,41 +0,0 @@
package com.audiobookshelf.app.managers
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class DownloadResumePolicyTest {
@Test
fun completeKnownFileDoesNotIssueRequest() {
assertEquals(
DownloadResumePolicy.InitialAction.COMPLETE,
DownloadResumePolicy.initialAction(100L, 100L))
}
@Test
fun partialAndUnknownFilesUseRange() {
assertEquals(
DownloadResumePolicy.InitialAction.RANGE_DOWNLOAD,
DownloadResumePolicy.initialAction(25L, 100L))
assertEquals(
DownloadResumePolicy.InitialAction.RANGE_DOWNLOAD,
DownloadResumePolicy.initialAction(25L, 0L))
}
@Test
fun oversizedFileRestartsAndEmptyFileDownloadsFully() {
assertEquals(
DownloadResumePolicy.InitialAction.RESTART,
DownloadResumePolicy.initialAction(101L, 100L))
assertEquals(
DownloadResumePolicy.InitialAction.FULL_DOWNLOAD,
DownloadResumePolicy.initialAction(0L, 100L))
}
@Test
fun parsesUnsatisfiedContentRange() {
assertEquals(787913771L, DownloadResumePolicy.unsatisfiedRangeSize("bytes */787913771"))
assertNull(DownloadResumePolicy.unsatisfiedRangeSize("bytes 0-99/100"))
assertNull(DownloadResumePolicy.unsatisfiedRangeSize(null))
}
}