Refine HttpEngineCoroutinizer

This commit is contained in:
tarsin committed 2023-06-18 16:13:54 +08:00
1 parent fe7c979d78
commit 110fb9fb59
3 files changed
+61 -47

No files matched your search

@@ -63,7 +63,7 @@ const val TAG_CALL = "CancellableCallReadingScope"
// Since Okio is not suspendable/cancellable, also okhttp, [executeAsync] only make [Request -> Response] cancellable, reading a Response is still not cancellable
// Considering interrupting is not safe and performant, See https://github.com/Kotlin/kotlinx.coroutines/issues/3551#issuecomment-1353245978, we have such [usingCancellable]
// TODO: Remove logging after this function stable
suspend inline fun <R> Call.usingCancellable(crossinline block: Response.() -> R): R = executeAsync().use {
suspend inline fun <R> Call.usingCancellable(crossinline block: suspend Response.() -> R): R = executeAsync().use {
val call = this
Log.d(TAG_CALL, "Got response of call$call")
coroutineScope {
@@ -101,7 +101,7 @@ suspend inline fun <R> Call.usingCancellable(crossinline block: Response.() -> R
}
suspend inline fun <R> Request.execute(block: Response.() -> R) = okHttpClient.newCall(this).executeAsync().use(block)
suspend inline fun <R> Request.executeNonCache(crossinline block: Response.() -> R) = nonCacheOkHttpClient.newCall(this).usingCancellable(block)
suspend inline fun <R> Request.executeNonCache(crossinline block: suspend Response.() -> R) = 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"
const val CHROME_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
@@ -9,6 +9,8 @@ import android.net.http.UrlResponseInfo
import android.os.Build
import androidx.annotation.RequiresExtension
import com.hippo.ehviewer.EhApplication
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx
import java.nio.ByteBuffer
@@ -29,7 +31,8 @@ class CronetRequest {
lateinit var mConsumer: (UrlResponseInfo, ByteBuffer) -> Unit
lateinit var onResponse: CronetRequest.(UrlResponseInfo) -> Unit
lateinit var request: UrlRequest
lateinit var mCont: Continuation<Boolean>
lateinit var daemonCont: Continuation<Boolean>
lateinit var readerCont: Continuation<Unit>
val callback = object : UrlRequest.Callback {
override fun onRedirectReceived(p0: UrlRequest, p1: UrlResponseInfo, p2: String) {
// No-op
@@ -37,7 +40,6 @@ class CronetRequest {
override fun onResponseStarted(p0: UrlRequest, p1: UrlResponseInfo) {
onResponse(p1)
request.read(buffer)
}
override fun onReadCompleted(p0: UrlRequest, p1: UrlResponseInfo, p2: ByteBuffer) {
@@ -52,11 +54,12 @@ class CronetRequest {
override fun onSucceeded(p0: UrlRequest, p1: UrlResponseInfo) {
val length = p1.receivedByteCount
// TODO: validate body length
mCont.resume(true)
readerCont.resume(Unit)
daemonCont.resume(true)
}
override fun onFailed(p0: UrlRequest, p1: UrlResponseInfo?, p2: HttpException) {
mCont.resumeWithException(p2)
daemonCont.resumeWithException(p2)
}
override fun onCanceled(p0: UrlRequest, p1: UrlResponseInfo?) {
@@ -72,15 +75,25 @@ inline fun cronetRequest(url: String, conf: UrlRequest.Builder.() -> Unit) = Cro
request = cronetHttpClient.newUrlRequestBuilder(url, cronetHttpClientExecutor, callback).apply(conf).build()
}
infix fun CronetRequest.consumeBody(callback: (UrlResponseInfo, ByteBuffer) -> Unit) = apply {
suspend infix fun CronetRequest.consumeBody(callback: (UrlResponseInfo, ByteBuffer) -> Unit) = apply {
mConsumer = callback
suspendCancellableCoroutine { cont ->
readerCont = cont
request.read(buffer)
}
}
suspend inline infix fun CronetRequest.execute(noinline callback: CronetRequest.(UrlResponseInfo) -> Unit): Boolean {
onResponse = callback
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { request.cancel() }
mCont = cont
request.start()
suspend inline infix fun CronetRequest.execute(noinline callback: suspend CronetRequest.(UrlResponseInfo) -> Unit): Boolean {
return coroutineScope {
onResponse = {
launch {
callback(it)
}
}
suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { request.cancel() }
daemonCont = cont
request.start()
}
}
}
@@ -26,7 +26,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.ehRequest
import com.hippo.ehviewer.client.executeNonCache
import com.hippo.ehviewer.client.getImageKey
import com.hippo.ehviewer.coil.edit
import com.hippo.ehviewer.coil.read
import com.hippo.ehviewer.coil.suspendEdit
import com.hippo.ehviewer.download.downloadLocation
@@ -43,10 +42,7 @@ import okio.Buffer
import okio.ForwardingSink
import okio.sink
import java.io.IOException
import java.nio.channels.FileChannel
import java.nio.file.StandardOpenOption
import java.util.Locale
import kotlin.io.path.deleteIfExists
import kotlin.io.path.readText
import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache
@@ -137,24 +133,21 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
notifyProgress: (Long, Long, Int) -> Unit,
): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 7) {
val key = getImageKey(mGid, index)
sCache.suspendEdit(key) {
val path = data.toNioPath()
path.deleteIfExists()
@Suppress("BlockingMethodInNonBlockingContext")
FileChannel.open(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE_NEW).use { chan ->
cronetRequest(url) {
referer?.let { addHeader("Referer", it) }
} execute {
val headers = it.headers.asMap
val type = headers["Content-Type"]?.first()?.toMediaType()?.subtype ?: "jpg"
val length = headers["Content-Length"]!!.first().toLong()
metadata.toFile().writeText(type)
cronetRequest(url) {
referer?.let { addHeader("Referer", it) }
} execute { info ->
val headers = info.headers.asMap
val type = headers["Content-Type"]?.first()?.toMediaType()?.subtype ?: "jpg"
val length = headers["Content-Length"]!!.first().toLong()
saveResponseMeta(index, type, length) { file ->
file.openOutputStream().use {
val chan = it.channel
consumeBody { info, buffer ->
val read = chan.write(buffer)
notifyProgress(length, info.receivedByteCount, read)
}
}
file.length()
}
}
} else {
@@ -168,7 +161,30 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
}
}
private fun saveFromHttpResponse(index: Int, response: Response, notifyProgress: (Long, Long, Int) -> Unit): Boolean {
private suspend fun saveResponseMeta(index: Int, ext: String, length: Long, fops: suspend (UniFile) -> Long): Boolean {
suspend fun realFops(f: UniFile) = fops(f).apply {
if (ext.lowercase() == "gif") {
f.openFileDescriptor("rw").use { rewriteGifSource2(it.fd) }
}
}
findDownloadFileForIndex(index, ext)?.run {
return realFops(this) == length
}
// Read Mode, allow save to cache
if (mode == SpiderQueen.MODE_READ) {
val key = getImageKey(mGid, index)
var received = 0L
sCache.suspendEdit(key) {
metadata.toFile().writeText(ext)
received = realFops(UniFile.fromFile(data.toFile())!!)
}
return received == length
}
return false
}
private suspend 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()
@@ -188,22 +204,7 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
}
}
findDownloadFileForIndex(index, extension)?.run {
return doSave(this) == length
}
// Read Mode, allow save to cache
if (mode == SpiderQueen.MODE_READ) {
val key = getImageKey(mGid, index)
var received: Long = 0
sCache.edit(key) {
metadata.toFile().writeText(extension)
received = doSave(UniFile.fromFile(data.toFile())!!)
}
return received == length
}
return false
return saveResponseMeta(index, extension, length) { doSave(it) }
}
fun saveToUniFile(index: Int, file: UniFile): Boolean {