diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt b/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt index d8198df00..7a8449cdb 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt @@ -17,6 +17,10 @@ package com.hippo.ehviewer.client import com.hippo.ehviewer.EhApplication.Companion.nonCacheOkHttpClient import com.hippo.ehviewer.EhApplication.Companion.okHttpClient +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.Call import okhttp3.FormBody import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -29,6 +33,8 @@ import org.json.JSONArray import org.json.JSONObject import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException inline fun ehRequest(url: String, referer: String? = null, origin: String? = null, builder: Request.Builder.() -> Unit = {}) = Request.Builder().url(url).apply { addHeader("User-Agent", CHROME_USER_AGENT) @@ -50,18 +56,33 @@ fun jsonArrayOf(vararg element: Any?) = JSONArray().apply { element.forEach { pu inline fun Request.Builder.jsonBody(builder: JSONObject.() -> Unit) = post(JSONObject().apply(builder).toString().toRequestBody(MEDIA_TYPE_JSON)) -suspend inline fun Request.execute(block: Response.() -> R): R { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) +suspend inline fun Call.usingCancellable(crossinline block: Response.() -> R): R = executeAsync().use { + coroutineScope { + suspendCancellableCoroutine { cont -> + launch { + val r = runCatching { block(it) } + if (!isCanceled() && cont.isActive) { + r.exceptionOrNull()?.let { cont.resumeWithException(it) } + r.getOrNull()?.let { cont.resume(it) } + } + } + cont.invokeOnCancellation { cancel() } + } } - return okHttpClient.newCall(this).executeAsync().use(block) } -suspend inline fun Request.executeNonCache(block: Response.() -> R): R { +suspend inline fun Request.execute(crossinline block: Response.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return nonCacheOkHttpClient.newCall(this).executeAsync().use(block) + return okHttpClient.newCall(this).usingCancellable(block) +} + +suspend inline fun Request.executeNonCache(crossinline block: Response.() -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return nonCacheOkHttpClient.newCall(this).usingCancellable(block) } const val CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36" diff --git a/app/src/main/java/com/hippo/ehviewer/coil/DiskCacheScoped.kt b/app/src/main/java/com/hippo/ehviewer/coil/DiskCacheScoped.kt index 6e52e9972..8f08b41f1 100644 --- a/app/src/main/java/com/hippo/ehviewer/coil/DiskCacheScoped.kt +++ b/app/src/main/java/com/hippo/ehviewer/coil/DiskCacheScoped.kt @@ -2,7 +2,7 @@ package com.hippo.ehviewer.coil import coil.disk.DiskCache -suspend inline fun DiskCache.edit(key: String, crossinline block: suspend DiskCache.Editor.() -> Unit): Boolean { +inline fun DiskCache.edit(key: String, crossinline block: DiskCache.Editor.() -> Unit): Boolean { val editor = openEditor(key) ?: return false editor.runCatching { block(this) diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/EhPageLoader.kt b/app/src/main/java/com/hippo/ehviewer/gallery/EhPageLoader.kt index d53137eca..36402cfa4 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/EhPageLoader.kt +++ b/app/src/main/java/com/hippo/ehviewer/gallery/EhPageLoader.kt @@ -35,8 +35,8 @@ class EhPageLoader(private val mGalleryInfo: GalleryInfo) : PageLoader2(), OnSpi override fun stop() { super.stop() mSpiderQueen.removeOnSpiderListener(this) - // Activity recreate may called, so wait 3000s - SimpleHandler.getInstance().postDelayed(ReleaseTask(mSpiderQueen), 3000) + // Activity recreate may called, so wait 0.5s + SimpleHandler.getInstance().postDelayed(ReleaseTask(mSpiderQueen), 500) } override val startPage diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index d3d982e96..9ff0dcf2d 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -34,7 +34,6 @@ import com.hippo.ehviewer.util.sendTo import com.hippo.ehviewer.yorozuya.FileUtils import com.hippo.unifile.UniFile import com.hippo.unifile.openOutputStream -import moe.tarsin.coroutines.runInterruptibleOkio import moe.tarsin.coroutines.runSuspendCatching import okhttp3.Response import okio.buffer @@ -132,35 +131,36 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { ): Boolean { // TODO: Use HttpEngine[https://developer.android.com/reference/android/net/http/HttpEngine] directly here if available // Since we don't want unnecessary copy between jvm heap & native heap - ehRequest(url, referer).executeNonCache { - if (code >= 400) return false - return saveFromHttpResponse(index, this, notifyProgress) + return ehRequest(url, referer).executeNonCache { + if (code >= 400) { + false + } else { + saveFromHttpResponse(index, this, notifyProgress) + } } } - private suspend fun saveFromHttpResponse(index: Int, response: Response, notifyProgress: (Long, Long, Int) -> Unit): Boolean { + private fun saveFromHttpResponse(index: Int, response: Response, notifyProgress: (Long, Long, Int) -> Unit): Boolean { val contentType = response.body.contentType() val extension = contentType?.subtype ?: "jpg" val length = response.body.contentLength() - suspend fun doSave(outFile: UniFile): Long { + fun doSave(outFile: UniFile): Long { var ret = 0L - runInterruptibleOkio { - outFile.openOutputStream().sink().buffer().use { sink -> - response.body.source().use { source -> - while (true) { - val bytesRead = source.read(sink.buffer, 8192) - if (bytesRead == -1L) break - ret += bytesRead - sink.emitCompleteSegments() - notifyProgress(length, ret, bytesRead.toInt()) - } + outFile.openOutputStream().sink().buffer().use { sink -> + response.body.source().use { source -> + while (true) { + val bytesRead = source.read(sink.buffer, 8192) + if (bytesRead == -1L) break + ret += bytesRead + sink.emitCompleteSegments() + notifyProgress(length, ret, bytesRead.toInt()) } } - if (extension.lowercase() == "gif") { - outFile.openFileDescriptor("rw").use { - rewriteGifSource2(it.fd) - } + } + if (extension.lowercase() == "gif") { + outFile.openFileDescriptor("rw").use { + rewriteGifSource2(it.fd) } } return ret diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt index de8ec7952..77d51fcfc 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt @@ -38,7 +38,7 @@ fun SpiderInfo.write(file: UniFile) { } } -suspend fun SpiderInfo.saveToCache() { +fun SpiderInfo.saveToCache() { runSuspendCatching { spiderInfoCache.edit(gid.toString()) { data.toFile().writeBytes(Cbor.encodeToByteArray(this@saveToCache)) diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.kt index 41a2aa60e..e63b03a61 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -49,7 +49,6 @@ import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withLock @@ -379,13 +378,13 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS galleryInfo.gid, galleryInfo.token!!, ) - try { + return try { ehRequest(url, referer).execute { val bodyStr = body.string() GalleryMultiPageViewerPTokenParser.parse(bodyStr).forEachIndexed { index, s -> spiderInfo.pTokenMap[index] = s } - return spiderInfo.pTokenMap[index] + spiderInfo.pTokenMap[index] } } catch (e: Throwable) { ExceptionUtils.throwIfFatal(e) @@ -412,14 +411,14 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS previewIndex, false, ) - try { + return try { ehRequest(url, referer).execute { readPreviews(body.string(), previewIndex, spiderInfo) - return spiderInfo.pTokenMap[index] + spiderInfo.pTokenMap[index] } } catch (e: Throwable) { ExceptionUtils.throwIfFatal(e) - return null + null } } @@ -427,7 +426,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS private fun writeSpiderInfoToLocal() { if (!isReady) return mSpiderDen.downloadDir?.run { createFile(SPIDER_INFO_FILENAME).also { mSpiderInfo.write(it) } } - runBlocking { mSpiderInfo.saveToCache() } + mSpiderInfo.saveToCache() } private fun isStateDone(state: Int): Boolean { diff --git a/app/src/main/java/moe/tarsin/coroutines/Interruptible.kt b/app/src/main/java/moe/tarsin/coroutines/Interruptible.kt deleted file mode 100644 index 2cb9b24d5..000000000 --- a/app/src/main/java/moe/tarsin/coroutines/Interruptible.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.tarsin.coroutines - -import kotlinx.coroutines.runInterruptible -import java.io.InterruptedIOException -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -// See https://github.com/Kotlin/kotlinx.coroutines/issues/3551 -suspend inline fun runInterruptibleOkio( - context: CoroutineContext = EmptyCoroutineContext, - crossinline block: () -> T, -): T = runInterruptible(context) { - try { - block() - } catch (e: InterruptedIOException) { - if (Thread.currentThread().isInterrupted) { - // Coroutine cancelled - throw InterruptedException().initCause(e) - } else { - // AsyncTimeout reached - throw e - } - } -}