Introduce Call.usingCancellable

This commit is contained in:
tarsin committed 2023-05-29 17:02:46 +08:00
1 parent 684d25780e
commit 29fb40eecc
7 files changed
+57 -61

No files matched your search

@@ -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 <R> Request.execute(block: Response.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
suspend inline fun <R> 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 <R> Request.executeNonCache(block: Response.() -> R): R {
suspend inline fun <R> 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 <R> 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"
@@ -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)
@@ -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
@@ -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
@@ -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))
@@ -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 {
@@ -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 <T> 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
}
}
}