diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt b/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt index f12968b4c..2d9e669c4 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt @@ -3,10 +3,14 @@ package com.hippo.ehviewer.ktor import com.hippo.ehviewer.client.EhCookieStore import io.ktor.client.HttpClientConfig import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.cookies.HttpCookies fun HttpClientConfig.configureCommon() = apply { install(HttpCookies) { storage = EhCookieStore } + install(HttpTimeout) { + requestTimeoutMillis = 10_000 + } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt b/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt index 8493eacc7..6feccaea2 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt @@ -12,6 +12,7 @@ import android.os.Build import androidx.annotation.RequiresExtension import io.ktor.client.engine.HttpClientEngineBase import io.ktor.client.engine.callContext +import io.ktor.client.plugins.HttpTimeoutCapability import io.ktor.client.request.HttpRequestData import io.ktor.client.request.HttpResponseData import io.ktor.http.HttpHeaders @@ -41,6 +42,9 @@ import kotlinx.coroutines.suspendCancellableCoroutine class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") { // Limit thread to 1 since we are async & non-blocking override val dispatcher = Dispatchers.Default.limitedParallelism(1) + + override val supportedCapabilities = setOf(HttpTimeoutCapability) + private val executor = dispatcher.asExecutor() private val pool = DirectByteBufferPool(32) private val client by lazy { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt index 96356f3a5..5b7aa4d6a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -52,6 +52,7 @@ import com.hippo.files.openFileDescriptor import com.hippo.files.openOutputStream import eu.kanade.tachiyomi.util.system.logcat import io.ktor.client.plugins.onDownload +import io.ktor.client.plugins.timeout import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsChannel import io.ktor.client.statement.request @@ -71,6 +72,7 @@ class SpiderDen(val info: GalleryInfo) { private var tempDownloadDir: Path? = null private val saveAsCbz = Settings.saveAsCbz private val archiveName = "$gid.cbz" + private val downloadTimeout = Settings.downloadTimeout * 1000L private val lock = ReentrantReadWriteLock() @@ -174,6 +176,9 @@ class SpiderDen(val info: GalleryInfo) { notifyProgress(total!!, done, (done - prev).toInt()) prev = done } + timeout { + requestTimeoutMillis = downloadTimeout + } }.executeSafely { if (it.status.isSuccess()) { saveFromHttpResponse(index, it) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index 8c8fedcde..7a9bfb641 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -42,14 +42,12 @@ import com.hippo.files.find import eu.kanade.tachiyomi.util.system.logcat import java.util.concurrent.atomic.AtomicInteger import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext @@ -60,7 +58,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit -import kotlinx.coroutines.withTimeout import moe.tarsin.coroutines.runSuspendCatching import okio.Path import splitties.init.appCtx @@ -502,7 +499,6 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS private var showKey: String? = null private val showKeyLock = Mutex() private val mDownloadDelay = Settings.downloadDelay.milliseconds - private val downloadTimeout = Settings.downloadTimeout.seconds private var lastRequestTime = TimeSource.Monotonic.markNow() var isDownloadMode = false private set @@ -670,14 +666,12 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS repeat(2) { times -> runCatching { logcat(WORKER_DEBUG_TAG) { "Start download image $index attempt #$times" } - val success = withTimeout(downloadTimeout) { - mSpiderDen.makeHttpCallAndSaveImage( - index, - targetImageUrl, - referer, - this@SpiderQueen::notifyPageDownload.partially1(index), - ) - } + val success = mSpiderDen.makeHttpCallAndSaveImage( + index, + targetImageUrl, + referer, + this@SpiderQueen::notifyPageDownload.partially1(index), + ) check(success) logcat(WORKER_DEBUG_TAG) { "Download image $index succeed" } @@ -686,11 +680,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS }.onFailure { mSpiderDen.removeIntermediateFiles(index) logcat(WORKER_DEBUG_TAG) { "Download image $index attempt #$times failed" } - error = when (it) { - is TimeoutCancellationException -> ERROR_TIMEOUT - is CancellationException -> throw it - else -> it.displayString() - } + if (it is CancellationException) throw it + error = it.displayString() } } } @@ -758,7 +749,6 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS } private val PTOKEN_FAILED_MESSAGE = appCtx.getString(R.string.error_get_ptoken_error) -private val ERROR_TIMEOUT = appCtx.getString(R.string.error_timeout) private val DECODE_ERROR = appCtx.getString(R.string.error_decoding_failed) private val URL_509_PATTERN = Regex("\\.org/.+/509s?\\.gif") private const val FORCE_RETRY = "Force retry" diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/ExceptionUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/ExceptionUtils.kt index a2c339ca2..956f13efa 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/ExceptionUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/ExceptionUtils.kt @@ -17,9 +17,18 @@ package com.hippo.ehviewer.util import com.hippo.ehviewer.R import eu.kanade.tachiyomi.util.system.logcat +import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.client.network.sockets.SocketTimeoutException +import io.ktor.client.plugins.HttpRequestTimeoutException import splitties.init.appCtx -fun Throwable.displayString(): String { - logcat(this) - return message ?: appCtx.getString(R.string.error_unknown) +fun Throwable.displayString(): String = when (this) { + is HttpRequestTimeoutException, + is ConnectTimeoutException, is SocketTimeoutException, + -> appCtx.getString(R.string.error_timeout) + + else -> { + logcat(this) + message ?: appCtx.getString(R.string.error_unknown) + } }