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 eacdb950a..abee40360 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ktor/Common.kt @@ -1,11 +1,11 @@ package com.hippo.ehviewer.ktor import com.hippo.ehviewer.BuildConfig -import com.hippo.ehviewer.Settings 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.HttpTimeoutConfig import io.ktor.client.plugins.SaveBodyPlugin import io.ktor.client.plugins.UserAgent import io.ktor.client.plugins.cookies.HttpCookies @@ -22,7 +22,7 @@ fun HttpClientConfig.configureCommon(redirect: B storage = EhCookieStore } install(HttpTimeout) { - connectTimeoutMillis = Settings.connTimeout * 1000L + reset() requestTimeoutMillis = 10_000 } install(UserAgent) { @@ -37,3 +37,9 @@ fun HttpClientConfig.configureCommon(redirect: B disabled = true } } + +fun HttpTimeoutConfig.reset() = apply { + requestTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS + connectTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS + socketTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS +} 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 be094e6cc..ee2fdd69f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ktor/CronetEngine.kt @@ -14,7 +14,6 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.util.restartApplication import io.ktor.client.engine.HttpClientEngineBase import io.ktor.client.engine.callContext -import io.ktor.client.plugins.ConnectTimeoutException import io.ktor.client.plugins.HttpTimeoutCapability import io.ktor.client.request.HttpRequestData import io.ktor.client.request.HttpResponseData @@ -35,14 +34,12 @@ import java.nio.ByteBuffer import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.job import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull import splitties.preferences.edit class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") { @@ -58,12 +55,7 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr } @InternalAPI - override suspend fun execute(data: HttpRequestData) = data.getCapabilityOrNull(HttpTimeoutCapability)!!.let { cfg -> - val connTimeout = cfg.connectTimeoutMillis!! - withTimeoutOrNull(connTimeout.milliseconds) { - executeHttpRequest(callContext(), data) - } ?: throw ConnectTimeoutException(data) - } + override suspend fun execute(data: HttpRequestData) = executeHttpRequest(callContext(), data) private suspend fun executeHttpRequest( callContext: CoroutineContext, 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 7cc8cb0c2..e0d26e981 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -166,6 +166,7 @@ class SpiderDen(val info: GalleryInfo) { referer: String?, notifyProgress: (Long, Long, Int) -> Unit, ) = timeoutBySpeed( + url, { ehRequest(url, referer, builder = it) }, { a, b, c -> notifyProgress(a, b, c) }, { resp -> check(saveFromHttpResponse(index, resp)) }, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SpeedTracker.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SpeedTracker.kt index 1dd05abcb..792edb0f1 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SpeedTracker.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SpeedTracker.kt @@ -5,19 +5,19 @@ import androidx.collection.mutableObjectIntMapOf import arrow.fx.coroutines.fixedRate import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.executeSafely +import com.hippo.ehviewer.ktor.reset import com.hippo.ehviewer.util.LowSpeedException import com.hippo.ehviewer.util.ensureSuccess +import io.ktor.client.plugins.ConnectTimeoutException import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.timeout import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpStatement -import io.ktor.client.statement.request import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource -import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.map @@ -57,40 +57,40 @@ class SpeedTracker(val window: Duration = 1.seconds) { } suspend inline fun timeoutBySpeed( - crossinline req: suspend (HttpRequestBuilder.() -> Unit) -> HttpStatement, + url: String, + crossinline request: suspend (HttpRequestBuilder.() -> Unit) -> HttpStatement, crossinline l: suspend (Long, Long, Int) -> Unit, crossinline f: suspend (HttpResponse) -> R, -) = with(SpeedTracker(2.seconds)) { - var prev = 0L - req { +) = coroutineScope { + val watchdog = launch { + delay(Settings.connTimeout.seconds) + throw ConnectTimeoutException(url, Settings.connTimeout * 1000L) + } + val tracker = SpeedTracker(2.seconds) + request { + var prev = 0L onDownload { done, total -> val bytesRead = (done - prev).toInt() - track(bytesRead) + tracker.track(bytesRead) l(total!!, done, bytesRead) prev = done } - timeout { connectTimeoutMillis = Settings.connTimeout * 1000L } + timeout { reset() } }.executeSafely { resp -> + watchdog.cancel() resp.status.ensureSuccess() - val timeoutSpeed = Settings.timeoutSpeed * 1024.0 - coroutineScope { - val work = async { f(resp) } - val job = launch { - delay(2.seconds) // Tolerant 4 secs for receiving - speedFlow(1.seconds).collect { speed -> - if (timeoutSpeed != 0.0 && speed < timeoutSpeed) { - throw LowSpeedException( - resp.request.url.toString(), - speed.toLong(), - ) - } + val speedWatchdog = launch { + val timeoutSpeed = Settings.timeoutSpeed * 1024.0 + tracker.speedFlow(1.seconds).collect { speed -> + if (timeoutSpeed != 0.0 && speed < timeoutSpeed) { + throw LowSpeedException(url, speed.toLong()) } } - try { - work.await() - } finally { - job.cancel() - } + } + try { + f(resp) + } finally { + speedWatchdog.cancel() } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt b/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt index a0623989f..ce663e59c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt @@ -69,6 +69,7 @@ object AppUpdater { } suspend fun downloadUpdate(url: String, path: Path) = timeoutBySpeed( + url, { ghStatement(url, builder = it) }, { _, _, _ -> }, { response ->