Refine HttpEngineCoroutinizer
This commit is contained in:
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 {
|
||||
|
||||
Reference in new issue
Block a user