Introduce Call.usingCancellable
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user