Files
futon-niadd-gitea-ci/patches/futon-niadd-range-download.patch
T
gandc 008379a09a
Build Futon with Niadd patch / Build Futon Niadd debug APK (push) Failing after 10m25s
fix
2026-08-10 01:22:32 +03:00

151 lines
5.2 KiB
Diff

--- a/app/src/main/kotlin/io/github/landwarderer/futon/reader/domain/PageLoader.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/reader/domain/PageLoader.kt
@@ -30,6 +30,7 @@
import io.github.landwarderer.futon.core.ui.image.TrimTransformation
import io.github.landwarderer.futon.core.util.FileSize
import io.github.landwarderer.futon.core.util.MimeTypes
+import io.github.landwarderer.futon.core.util.ext.MimeType
import io.github.landwarderer.futon.core.util.ext.URI_SCHEME_ZIP
import io.github.landwarderer.futon.core.util.ext.cancelChildrenAndJoin
import io.github.landwarderer.futon.core.util.ext.compressToPNG
@@ -52,6 +53,7 @@
import io.github.landwarderer.futon.local.data.LocalStorageCache
import io.github.landwarderer.futon.local.data.PageCache
import org.koitharu.kotatsu.parsers.model.MangaPage
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -70,6 +72,9 @@
import kotlinx.coroutines.sync.withPermit
import okhttp3.OkHttpClient
import okhttp3.Request
+import okio.buffer
+import okio.sink
+import okio.source
import okio.use
import org.jetbrains.annotations.Blocking
import java.io.File
@@ -97,6 +102,7 @@
private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()
private val semaphore = Semaphore(3)
+ private val niaddSemaphore = Semaphore(1)
private val convertLock = Mutex()
private val prefetchLock = Mutex()
@@ -303,13 +309,100 @@
if (isPrefetch) {
downloadSlowdownDispatcher.delay(page.source)
}
- val request = createPageRequest(pageUrl, page.source)
- imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
- response.requireBody().withProgress(progress).use {
- cache.set(pageUrl, it.source(), it.contentType()?.toMimeType())
+ if (page.source == MangaParserSource.NINEMANGA_RU) {
+ niaddSemaphore.withPermit {
+ downloadNiaddPageByRanges(pageUrl, page.source, progress)
}
- }.toUri()
+ } else {
+ val request = createPageRequest(pageUrl, page.source)
+ imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
+ response.requireBody().withProgress(progress).use {
+ cache.set(pageUrl, it.source(), it.contentType()?.toMimeType())
+ }
+ }.toUri()
+ }
+ }
+ }
+ }
+
+ private suspend fun downloadNiaddPageByRanges(
+ pageUrl: String,
+ source: MangaSource,
+ progress: MutableStateFlow<Float>,
+ ): Uri {
+ val tempFile = File.createTempFile(
+ prefix = "niadd-page-",
+ suffix = ".part",
+ directory = context.cacheDir,
+ )
+ var mimeType: MimeType? = null
+ try {
+ var offset = 0L
+ var totalBytes = -1L
+
+ tempFile.sink(append = false).buffer().use { sink ->
+ while (totalBytes < 0L || offset < totalBytes) {
+ val requestedEnd = if (totalBytes > 0L) {
+ minOf(offset + NIADD_RANGE_CHUNK_SIZE - 1L, totalBytes - 1L)
+ } else {
+ offset + NIADD_RANGE_CHUNK_SIZE - 1L
+ }
+ val request = createPageRequest(pageUrl, source)
+ .newBuilder()
+ .header("Range", "bytes=$offset-$requestedEnd")
+ .header("Connection", "close")
+ .build()
+
+ okHttp.newCall(request).execute().use { response ->
+ check(response.code == 206) {
+ "Niadd CDN ignored range request: HTTP ${response.code} for $pageUrl"
+ }
+
+ val contentRange = response.header("Content-Range")
+ ?: error("Niadd CDN response has no Content-Range for $pageUrl")
+ val responseTotal = contentRange
+ .substringAfterLast('/', "")
+ .toLongOrNull()
+ ?.takeIf { it > 0L }
+ ?: error("Invalid Niadd Content-Range: $contentRange")
+
+ if (totalBytes < 0L) {
+ totalBytes = responseTotal
+ } else {
+ check(totalBytes == responseTotal) {
+ "Niadd CDN changed content size from $totalBytes to $responseTotal for $pageUrl"
+ }
+ }
+
+ val body = response.requireBody()
+ if (mimeType == null) {
+ mimeType = body.contentType()?.toMimeType()
+ }
+
+ val expectedEnd = minOf(requestedEnd, totalBytes - 1L)
+ val expectedBytes = expectedEnd - offset + 1L
+ val receivedBytes = sink.writeAll(body.source())
+ check(receivedBytes == expectedBytes) {
+ "Short Niadd range: expected $expectedBytes bytes, got $receivedBytes " +
+ "for bytes=$offset-$expectedEnd ($pageUrl)"
+ }
+
+ offset += receivedBytes
+ progress.value = (offset.toFloat() / totalBytes.toFloat())
+ .coerceIn(0f, 1f)
+ }
+ }
+ }
+
+ check(totalBytes > 0L && tempFile.length() == totalBytes) {
+ "Incomplete Niadd image: expected $totalBytes bytes, got ${tempFile.length()} for $pageUrl"
+ }
+
+ return tempFile.source().use { sourceStream ->
+ cache.set(pageUrl, sourceStream, mimeType).toUri()
}
+ } finally {
+ tempFile.delete()
}
}
@@ -340,6 +433,7 @@
companion object {
private const val PROGRESS_UNDEFINED = -1f
+ private const val NIADD_RANGE_CHUNK_SIZE = 16L * 1024L
private const val PREFETCH_LIMIT_DEFAULT = 6
private const val PREFETCH_MIN_RAM_MB = 80L