Bring back ktor CIO for image downloading
This commit is contained in:
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 {
|
||||
|
||||
Reference in new issue
Block a user