3 Commits
Author SHA1 Message Date
gandc 7c63edc664 fix patch
Build Futon with Niadd patch / Build Futon Niadd debug APK (push) Successful in 3m54s
2026-08-10 01:51:37 +03:00
gandc 83e7c698e1 fix SocketTimeoutException
Build Futon with Niadd patch / Build Futon Niadd debug APK (push) Failing after 26s
2026-08-10 01:48:15 +03:00
gandc d5f6d20e1f fix
Build Futon with Niadd patch / Build Futon Niadd debug APK (push) Successful in 4m3s
2026-08-10 01:35:30 +03:00
+141 -9
View File
@@ -1,6 +1,138 @@
diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/pager/pages/MangaPageFetcher.kt b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/pager/pages/MangaPageFetcher.kt
index ae55a0f..ddadcc1 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/pager/pages/MangaPageFetcher.kt
+++ b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/pager/pages/MangaPageFetcher.kt
@@ -15,21 +15,26 @@ import coil3.request.Options
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Response
+import okio.Buffer
import okio.FileSystem
import okio.Path.Companion.toOkioPath
import io.github.landwarderer.futon.core.network.MangaHttpClient
import io.github.landwarderer.futon.core.network.imageproxy.ImageProxyInterceptor
import io.github.landwarderer.futon.core.parser.MangaRepository
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.fetch
import io.github.landwarderer.futon.core.util.ext.isNetworkUri
import io.github.landwarderer.futon.core.util.ext.toMimeTypeOrNull
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.util.mimeType
import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import kotlinx.coroutines.sync.Semaphore
+import kotlinx.coroutines.sync.withPermit
import io.github.landwarderer.futon.reader.domain.PageLoader
import javax.inject.Inject
@@ -72,6 +77,12 @@ class MangaPageFetcher(
}
private suspend fun fetchPage(pageUrl: String): FetchResult {
+ if (page.source == MangaParserSource.NINEMANGA_RU) {
+ return niaddSemaphore.withPermit {
+ fetchNiaddPageByRanges(pageUrl)
+ }
+ }
+
val request = PageLoader.createPageRequest(pageUrl, page.source)
return imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
if (!response.isSuccessful) {
@@ -89,6 +100,73 @@ class MangaPageFetcher(
}
}
+ private suspend fun fetchNiaddPageByRanges(pageUrl: String): FetchResult {
+ val buffer = Buffer()
+ var mimeType: MimeType? = null
+ var offset = 0L
+ var totalBytes = -1L
+
+ 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 = PageLoader.createPageRequest(pageUrl, page.source)
+ .newBuilder()
+ .header("Range", "bytes=$offset-$requestedEnd")
+ .header("Connection", "close")
+ .build()
+
+ okHttpClient.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"
+ }
+ }
+
+ if (mimeType == null) {
+ mimeType = response.mimeType?.toMimeTypeOrNull()
+ }
+
+ val expectedEnd = minOf(requestedEnd, totalBytes - 1L)
+ val expectedBytes = expectedEnd - offset + 1L
+ val receivedBytes = buffer.writeAll(response.requireBody().source())
+ check(receivedBytes == expectedBytes) {
+ "Short Niadd range: expected $expectedBytes bytes, got $receivedBytes " +
+ "for bytes=$offset-$expectedEnd ($pageUrl)"
+ }
+
+ offset += receivedBytes
+ }
+ }
+
+ check(totalBytes > 0L && buffer.size == totalBytes) {
+ "Incomplete Niadd image: expected $totalBytes bytes, got ${buffer.size} for $pageUrl"
+ }
+
+ val file = pagesCache.set(pageUrl, buffer, mimeType)
+ return SourceFetchResult(
+ source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),
+ mimeType = mimeType?.toString(),
+ dataSource = DataSource.NETWORK,
+ )
+ }
+
private fun Response.toNetworkResponse() = NetworkResponse(
code = code,
requestMillis = sentRequestAtMillis,
@@ -106,6 +184,11 @@ class MangaPageFetcher(
return headers.build()
}
+ companion object {
+ private const val NIADD_RANGE_CHUNK_SIZE = 16L * 1024L
+ private val niaddSemaphore = Semaphore(1)
+ }
+
class Factory @Inject constructor(
@MangaHttpClient private val okHttpClient: OkHttpClient,
@PageCache private val pagesCache: LocalStorageCache,
diff --git 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
index 18d203d..6109efa 100644
--- a/app/src/main/kotlin/io/github/landwarderer/futon/reader/domain/PageLoader.kt --- 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 +++ b/app/src/main/kotlin/io/github/landwarderer/futon/reader/domain/PageLoader.kt
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@ import io.github.landwarderer.futon.core.prefs.AppSettings
import io.github.landwarderer.futon.core.ui.image.TrimTransformation import io.github.landwarderer.futon.core.ui.image.TrimTransformation
import io.github.landwarderer.futon.core.util.FileSize import io.github.landwarderer.futon.core.util.FileSize
import io.github.landwarderer.futon.core.util.MimeTypes import io.github.landwarderer.futon.core.util.MimeTypes
@@ -8,7 +140,7 @@
import io.github.landwarderer.futon.core.util.ext.URI_SCHEME_ZIP 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.cancelChildrenAndJoin
import io.github.landwarderer.futon.core.util.ext.compressToPNG import io.github.landwarderer.futon.core.util.ext.compressToPNG
@@ -52,6 +53,7 @@ @@ -52,6 +53,7 @@ import io.github.landwarderer.futon.download.ui.worker.DownloadSlowdownDispatche
import io.github.landwarderer.futon.local.data.LocalStorageCache import io.github.landwarderer.futon.local.data.LocalStorageCache
import io.github.landwarderer.futon.local.data.PageCache import io.github.landwarderer.futon.local.data.PageCache
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
@@ -16,7 +148,7 @@
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -70,6 +72,9 @@ @@ -70,6 +72,9 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@@ -26,7 +158,7 @@
import okio.use import okio.use
import org.jetbrains.annotations.Blocking import org.jetbrains.annotations.Blocking
import java.io.File import java.io.File
@@ -97,6 +102,7 @@ @@ -97,6 +102,7 @@ class PageLoader @Inject constructor(
private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>() private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()
private val semaphore = Semaphore(3) private val semaphore = Semaphore(3)
@@ -34,7 +166,7 @@
private val convertLock = Mutex() private val convertLock = Mutex()
private val prefetchLock = Mutex() private val prefetchLock = Mutex()
@@ -303,13 +309,100 @@ @@ -303,13 +309,100 @@ class PageLoader @Inject constructor(
if (isPrefetch) { if (isPrefetch) {
downloadSlowdownDispatcher.delay(page.source) downloadSlowdownDispatcher.delay(page.source)
} }
@@ -65,9 +197,9 @@
+ progress: MutableStateFlow<Float>, + progress: MutableStateFlow<Float>,
+ ): Uri { + ): Uri {
+ val tempFile = File.createTempFile( + val tempFile = File.createTempFile(
+ prefix = "niadd-page-", + "niadd-page-",
+ suffix = ".part", + ".part",
+ directory = context.cacheDir, + context.cacheDir,
+ ) + )
+ var mimeType: MimeType? = null + var mimeType: MimeType? = null
+ try { + try {
@@ -140,7 +272,7 @@
} }
} }
@@ -340,6 +433,7 @@ @@ -340,6 +433,7 @@ class PageLoader @Inject constructor(
companion object { companion object {
private const val PROGRESS_UNDEFINED = -1f private const val PROGRESS_UNDEFINED = -1f