Bring back ktor CIO for image downloading

This commit is contained in:
tarsin committed 2023-02-28 17:52:16 +08:00
1 parent 3f4efc51c9
commit 5c18dad1d8
9 files changed
+159 -219

No files matched your search

@@ -46,10 +46,16 @@ import com.hippo.yorozuya.IntIdGenerator
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.launchIO
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.cookies.HttpCookies
import kotlinx.coroutines.DelicateCoroutinesApi
import okhttp3.OkHttpClient
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
import java.security.KeyStore
import java.security.cert.X509Certificate
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
@@ -231,6 +237,53 @@ class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactor
builder.build()
}
val ktorClient by lazy {
HttpClient(CIO) {
engine {
proxy = when (Settings.proxyType) {
EhProxySelector.TYPE_DIRECT -> Proxy.NO_PROXY
EhProxySelector.TYPE_SYSTEM -> null
EhProxySelector.TYPE_HTTP -> {
val ip = Settings.proxyIp
val port = Settings.proxyPort
val iNetAddress = InetAddress.getByName(ip)
val socketAddress = InetSocketAddress(iNetAddress, port)
Proxy(Proxy.Type.HTTP, socketAddress)
}
// CIO does not support Socks proxy yet
else -> null
}
https {
serverName = "0.0.0.0".takeIf { Settings.dF }
trustManager = @SuppressLint("CustomX509TrustManager")
object : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(
p0: Array<out X509Certificate>?,
p1: String?
) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(
p0: Array<out X509Certificate>?,
p1: String?
) {
}
override fun getAcceptedIssuers(): Array<X509Certificate?> {
return arrayOfNulls(0)
}
}
}
pipelining = true
}
install(HttpCookies) {
storage = EhCookieStore
}
}
}
@JvmStatic
val galleryDetailCache by lazy {
LruCache<Long, GalleryDetail>(25).also {
@@ -18,6 +18,8 @@ package com.hippo.ehviewer.client
import com.hippo.ehviewer.EhApplication
import com.hippo.network.CookieDatabase
import com.hippo.network.CookieSet
import io.ktor.client.plugins.cookies.CookiesStorage
import io.ktor.http.Url
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
@@ -25,7 +27,9 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.Collections
import java.util.regex.Pattern
object EhCookieStore : CookieJar {
typealias KtorCookie = io.ktor.http.Cookie
object EhCookieStore : CookieJar, CookiesStorage {
private val db: CookieDatabase = CookieDatabase(EhApplication.application, "okhttp3-cookie.db")
private val map: MutableMap<String, CookieSet> = db.allCookies
@@ -178,9 +182,19 @@ object EhCookieStore : CookieJar {
db.clear()
}
@Synchronized
fun close() {
db.close()
override suspend fun addCookie(requestUrl: Url, cookie: io.ktor.http.Cookie) {}
// This should never be closed
override fun close() {}
private val okhttpCookieToKtorCookieMap = hashMapOf<Cookie, KtorCookie>()
override suspend fun get(requestUrl: Url): List<io.ktor.http.Cookie> {
return loadForRequest(requestUrl.toString().toHttpUrl()).map {
okhttpCookieToKtorCookieMap[it] ?: KtorCookie(
it.name,
it.value
).apply { okhttpCookieToKtorCookieMap[it] = this }
}
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
@@ -16,6 +16,7 @@
package com.hippo.ehviewer.client
import com.hippo.okhttp.ChromeRequestBuilder
import io.ktor.http.HttpMessageBuilder
class EhRequestBuilder @JvmOverloads constructor(
url: String,
@@ -27,3 +28,6 @@ class EhRequestBuilder @JvmOverloads constructor(
origin?.let { addHeader("Origin", it) }
}
}
fun HttpMessageBuilder.referer(value: String?): Unit =
value?.let { headers.append("Referer", it) } ?: Unit
@@ -18,39 +18,40 @@ package com.hippo.ehviewer.spider
import android.graphics.ImageDecoder
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_WRITE
import coil.decode.DecodeUtils
import coil.decode.FrameDelayRewritingSource
import coil.decode.isGif
import coil.disk.DiskCache
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.Companion.application
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhCacheKeyFactory
import com.hippo.ehviewer.client.EhRequestBuilder
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.referer
import com.hippo.ehviewer.coil.edit
import com.hippo.ehviewer.coil.read
import com.hippo.ehviewer.gallery.SUPPORT_IMAGE_EXTENSIONS
import com.hippo.image.Image
import com.hippo.image.Image.CloseableSource
import com.hippo.sendTo
import com.hippo.unifile.UniFile
import com.hippo.unifile.openOutputStream
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.MathUtils
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import io.ktor.client.plugins.onDownload
import io.ktor.client.request.prepareGet
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.ContentType
import io.ktor.http.contentLength
import io.ktor.http.contentType
import io.ktor.utils.io.jvm.nio.copyTo
import moe.tarsin.coroutines.runSuspendCatching
import okhttp3.executeAsync
import okio.buffer
import java.io.File
import java.io.IOException
import java.nio.channels.FileChannel
import java.util.Locale
import kotlin.io.path.readText
private val client = EhApplication.okHttpClient
private val client = EhApplication.ktorClient
class SpiderDen(private val mGalleryInfo: GalleryInfo) {
private val mGid: Long = mGalleryInfo.gid
@@ -168,61 +169,62 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
referer: String?,
notifyProgress: (Long, Long, Int) -> Unit
): Boolean {
val call = client.newCall(EhRequestBuilder(url, referer).build())
call.executeAsync().use { response ->
if (response.code >= 400) return false
val body = response.body
val extension = body.contentType()?.subtype ?: "jpg"
val length = body.contentLength()
suspend fun doSave(chan: FileChannel): Long {
var receivedSize: Long = 0
body.source().use { responseSource ->
val source = if (DecodeUtils.isGif(responseSource)) FrameDelayRewritingSource(
responseSource
).buffer() else responseSource
source.use {
while (true) {
currentCoroutineContext().ensureActive()
val bytesRead = chan.transferFrom(it, receivedSize, TRANSFER_BLOCK)
receivedSize += bytesRead
notifyProgress(body.contentLength(), receivedSize, bytesRead.toInt())
if (bytesRead.toInt() == 0) break
}
}
}
return receivedSize
return client.prepareGet(url) {
var state: Long = 0
referer(referer)
onDownload { bytesSentTotal, contentLength ->
notifyProgress(contentLength, bytesSentTotal, (bytesSentTotal - state).toInt())
state = bytesSentTotal
}
}.execute {
if (it.status.value >= 400) return@execute false
saveFromHttpResponse(index, it)
}
}
findDownloadFileForIndex(index, extension)?.runSuspendCatching {
openOutputStream().use { outputStream ->
outputStream.channel.use { return doSave(it) == length }
}
}?.onFailure {
it.printStackTrace()
return false
}
private suspend fun saveFromHttpResponse(index: Int, body: HttpResponse): Boolean {
val contentType = body.contentType()
val extension = contentType?.contentSubtype ?: "jpg"
val length = body.contentLength() ?: return false
// Read Mode, allow save to cache
if (mMode == SpiderQueen.MODE_READ) {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
var received: Long = 0
runSuspendCatching {
sCache.edit(key) {
metadata.toFile().writeText(extension)
data.toFile().outputStream().use { outputStream ->
outputStream.channel.use { received = doSave(it) }
}
}
}.onFailure {
it.printStackTrace()
}.onSuccess {
return received == length
suspend fun doSave(outFile: UniFile): Long {
val ret: Long
outFile.openOutputStream().use { outputStream ->
outputStream.channel.use {
ret = body.bodyAsChannel().copyTo(it)
}
}
if (contentType == ContentType.Image.GIF)
outFile.openFileDescriptor("rw").use {
Image.rewriteGifSource2(it.fd)
}
return ret
}
findDownloadFileForIndex(index, extension)?.runSuspendCatching {
return doSave(this) == length
}?.onFailure {
it.printStackTrace()
return false
}
// Read Mode, allow save to cache
if (mMode == SpiderQueen.MODE_READ) {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
var received: Long = 0
runSuspendCatching {
sCache.edit(key) {
metadata.toFile().writeText(extension)
received = doSave(UniFile.fromFile(data.toFile())!!)
}
}.onFailure {
it.printStackTrace()
}.onSuccess {
return received == length
}
}
return false
}
fun saveToUniFile(index: Int, file: UniFile): Boolean {
@@ -100,6 +100,9 @@ class Image private constructor(private val src: CloseableSource) {
@JvmStatic
external fun rewriteGifSource(buffer: ByteBuffer)
@JvmStatic
external fun rewriteGifSource2(fd: Int)
}
interface CloseableSource : AutoCloseable {