Initial support Android14 HttpEngine

This commit is contained in:
tarsin committed 2023-06-18 15:09:50 +08:00
1 parent ff89afb8c1
commit 0d10899417
3 files changed
+131 -7

No files matched your search

@@ -15,4 +15,17 @@ inline fun DiskCache.edit(key: String, crossinline block: DiskCache.Editor.() ->
return true
}
suspend inline fun DiskCache.suspendEdit(key: String, crossinline block: suspend DiskCache.Editor.() -> Unit): Boolean {
val editor = openEditor(key) ?: return false
editor.runCatching {
block(this)
}.onFailure {
editor.abort()
throw it
}.onSuccess {
editor.commit()
}
return true
}
inline fun <T> DiskCache.read(key: String, block: DiskCache.Snapshot.() -> T): T? = openSnapshot(key)?.use(block)
@@ -0,0 +1,85 @@
@file:RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
package com.hippo.ehviewer.spider
import android.net.http.HttpEngine
import android.net.http.HttpException
import android.net.http.UrlRequest
import android.net.http.UrlResponseInfo
import android.os.Build
import androidx.annotation.RequiresExtension
import com.hippo.ehviewer.EhApplication
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx
import java.nio.ByteBuffer
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
val cronetHttpClient = HttpEngine.Builder(appCtx).apply {
setEnableHttp2(true)
setEnableBrotli(true)
setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_DISABLED, 0)
setEnableQuic(true)
}.build()
val cronetHttpClientExecutor = EhApplication.nonCacheOkHttpClient.dispatcher.executorService
class CronetRequest {
lateinit var mConsumer: (UrlResponseInfo, ByteBuffer) -> Unit
lateinit var onResponse: (UrlResponseInfo) -> Unit
lateinit var request: UrlRequest
lateinit var mCont: Continuation<Boolean>
val callback = object : UrlRequest.Callback {
override fun onRedirectReceived(p0: UrlRequest, p1: UrlResponseInfo, p2: String) {
// No-op
}
override fun onResponseStarted(p0: UrlRequest, p1: UrlResponseInfo) {
request.read(buffer)
}
override fun onReadCompleted(p0: UrlRequest, p1: UrlResponseInfo, p2: ByteBuffer) {
p2.flip()
mConsumer(p1, p2)
cronetHttpClientExecutor.execute {
buffer.flip()
request.read(buffer)
}
}
override fun onSucceeded(p0: UrlRequest, p1: UrlResponseInfo) {
val length = p1.receivedByteCount
// TODO: validate body length
mCont.resume(true)
}
override fun onFailed(p0: UrlRequest, p1: UrlResponseInfo?, p2: HttpException) {
mCont.resumeWithException(p2)
}
override fun onCanceled(p0: UrlRequest, p1: UrlResponseInfo?) {
// No-op
}
}
// TODO: Pool it
val buffer: ByteBuffer = ByteBuffer.allocateDirect(4096)
}
inline fun cronetRequest(url: String, conf: UrlRequest.Builder.() -> Unit) = CronetRequest().apply {
request = cronetHttpClient.newUrlRequestBuilder(url, cronetHttpClientExecutor, callback).apply(conf).build()
}
infix fun CronetRequest.onResponse(callback: (UrlResponseInfo) -> Unit) = apply {
onResponse = callback
}
suspend inline infix fun CronetRequest.execute(noinline consumer: (UrlResponseInfo, ByteBuffer) -> Unit): Boolean {
mConsumer = consumer
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { request.cancel() }
mCont = cont
request.start()
}
}
@@ -16,8 +16,10 @@
package com.hippo.ehviewer.spider
import android.graphics.ImageDecoder
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_WRITE
import android.os.ext.SdkExtensions
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
import com.hippo.ehviewer.client.data.GalleryInfo
@@ -26,6 +28,7 @@ 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
import com.hippo.ehviewer.gallery.SUPPORT_IMAGE_EXTENSIONS
import com.hippo.ehviewer.image.Image.CloseableSource
@@ -34,12 +37,16 @@ import com.hippo.ehviewer.util.sendTo
import com.hippo.ehviewer.yorozuya.FileUtils
import com.hippo.unifile.UniFile
import com.hippo.unifile.openOutputStream
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response
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
@@ -129,13 +136,32 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
referer: String?,
notifyProgress: (Long, Long, Int) -> Unit,
): 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
return ehRequest(url, referer).executeNonCache {
if (code >= 400) {
false
} else {
saveFromHttpResponse(index, this, notifyProgress)
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 {
cronetRequest(url) {
referer?.let { addHeader("Referer", it) }
} onResponse {
val type = it.headers.asMap["Content-Type"]?.first()?.toMediaType()?.subtype ?: "jpg"
metadata.toFile().writeText(type)
} execute { info, buffer ->
val read = it.write(buffer)
val length = info.headers.asMap["Content-Length"]!!.first().toLong()
notifyProgress(length, info.receivedByteCount, read)
}
}
}
} else {
ehRequest(url, referer).executeNonCache {
if (code >= 400) {
false
} else {
saveFromHttpResponse(index, this, notifyProgress)
}
}
}
}