Initial support Android14 HttpEngine
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user