From 057ba0c6e15a2e1a7881edff35eeec89ea02d85f Mon Sep 17 00:00:00 2001 From: tarsin Date: Sat, 4 Feb 2023 12:20:41 +0800 Subject: [PATCH] Migrate image downloading to ktor --- .../hippo/ehviewer/client/EhRequestBuilder.kt | 31 +++++---- .../com/hippo/ehviewer/spider/SpiderDen.kt | 63 ++++++++++++------- .../hippo/ehviewer/spider/SpiderQueen.java | 41 +++--------- 3 files changed, 66 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt b/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt index 5e81f4979..ea1c78af7 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/EhRequestBuilder.kt @@ -13,24 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.hippo.ehviewer.client -package com.hippo.ehviewer.client; +import com.hippo.okhttp.ChromeRequestBuilder +import io.ktor.http.HttpMessageBuilder -import com.hippo.okhttp.ChromeRequestBuilder; - -public class EhRequestBuilder extends ChromeRequestBuilder { - - public EhRequestBuilder(String url, String referer) { - this(url, referer, null); - } - - public EhRequestBuilder(String url, String referer, String origin) { - super(url); - if (referer != null) { - addHeader("Referer", referer); - } - if (origin != null) { - addHeader("Origin", origin); - } +class EhRequestBuilder @JvmOverloads constructor( + url: String, + referer: String?, + origin: String? = null +) : ChromeRequestBuilder(url) { + init { + referer?.let { addHeader("Referer", it) } + origin?.let { addHeader("Origin", it) } } } + +fun HttpMessageBuilder.referer(value: String?): Unit = + value?.let { headers.append("Referer", it) } ?: Unit diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index 3366cca5a..06ed895c8 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -15,16 +15,17 @@ */ package com.hippo.ehviewer.spider -import android.webkit.MimeTypeMap import coil.disk.DiskCache import com.hippo.Native.mapFd import com.hippo.Native.unmapDirectByteBuffer +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.EhUtils.getSuitableTitle import com.hippo.ehviewer.client.data.GalleryInfo +import com.hippo.ehviewer.client.referer import com.hippo.ehviewer.gallery.PageLoader2 import com.hippo.image.Image.ByteBufferSource import com.hippo.unifile.RawFile @@ -32,16 +33,27 @@ import com.hippo.unifile.UniFile import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.MathUtils import com.hippo.yorozuya.Utilities -import okhttp3.ResponseBody +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.contentLength +import io.ktor.http.contentType +import io.ktor.utils.io.jvm.nio.copyTo +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.io.IOException import java.io.RandomAccessFile import java.nio.ByteBuffer import java.nio.channels.FileChannel import java.util.Locale import kotlin.io.path.readText +private val client = EhApplication.ktorClient + class SpiderDen(private val mGalleryInfo: GalleryInfo) { private val mGid: Long = mGalleryInfo.gid private var mDownloadDir: UniFile? = getGalleryDownloadDir(mGid) @@ -173,36 +185,45 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { return dir.createFile(generateImageFilename(index, ext)) } - // TODO: Support cancellation after spiderqueen coroutinize - fun saveFromBufferedSource(index: Int, body: ResponseBody, notifyProgress: (Long, Long, Int) -> Unit): Long { - val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(body.contentType().toString()) ?: "jpg" - - fun doSave(outChannel: FileChannel): Long { - var receivedSize: Long = 0 - body.source().use { source -> - while (true) { - val bytesRead = outChannel.transferFrom(source, receivedSize, TRANSFER_BLOCK) - receivedSize += bytesRead - notifyProgress(body.contentLength(), receivedSize, bytesRead.toInt()) - if (bytesRead.toInt() == 0) break + @Throws(IOException::class) + fun makeHttpCallAndSaveImage(index: Int, url: String, referer: String?, notifyProgress: (Long, Long, Int) -> Unit): Boolean { + return runBlocking { + client.prepareGet(url) { + var state: Long = 0 + referer(referer) + onDownload { bytesSentTotal, contentLength -> + runCatching { notifyProgress(contentLength, bytesSentTotal, (bytesSentTotal - state).toInt()) } + .onFailure { cancel() } + state = bytesSentTotal } + }.execute { + if (it.status.value >= 400) return@execute false + saveFromHttpResponse(index, it) } - return receivedSize + } + } + + private suspend fun saveFromHttpResponse(index: Int, body: HttpResponse): Boolean { + val extension = body.contentType()?.contentSubtype ?: "jpg" + val length = body.contentLength() ?: return false + + suspend fun doSave(outChannel: FileChannel): Long { + return body.bodyAsChannel().copyTo(outChannel) } findDownloadFileForIndex(index, extension)?.runCatching { openOutputStream().use { outputStream -> - (outputStream as FileOutputStream).channel.use { return doSave(it) } + (outputStream as FileOutputStream).channel.use { return doSave(it) == length } } }?.onFailure { it.printStackTrace() - return -1 + return false } // Read Mode, allow save to cache if (mMode == SpiderQueen.MODE_READ) { val key = EhCacheKeyFactory.getImageKey(mGid, index) - val editor = sCache.edit(key) ?: return -1 + val editor = sCache.edit(key) ?: return false var received: Long = 0 runCatching { editor.metadata.toFile().writeText(extension) @@ -212,14 +233,14 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { }.onFailure { editor.abort() it.printStackTrace() - return -1 + return false }.onSuccess { editor.commit() - return received + return received == length } } - return -1 + return false } fun saveToUniFile(index: Int, file: UniFile): Boolean { diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index ef5524119..d0fefac03 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -68,7 +68,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import eu.kanade.tachiyomi.ui.reader.loader.PageLoader; -import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -1039,7 +1038,7 @@ public final class SpiderQueen implements Runnable { boolean forceHtml = false; boolean leakSkipHathKey = false; - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 2; i++) { String imageUrl = null; String localShowKey; @@ -1145,44 +1144,24 @@ public final class SpiderQueen implements Runnable { Log.d(TAG, "Start download image " + index); } - Call call = mHttpClient.newCall(new EhRequestBuilder(targetImageUrl, referer).build()); - Response response = call.execute(); - - if (response.code() >= 400) { - // Maybe 404 - response.close(); - error = "Bad code: " + response.code(); - forceHtml = true; - continue; - } - - var body = response.body(); - var received = mSpiderDen.saveFromBufferedSource(index, body , (contentLength, receivedSize, bytesRead) -> { + var success = mSpiderDen.makeHttpCallAndSaveImage(index, targetImageUrl, referer, (contentLength, receivedSize, bytesRead) -> { if (mStoped) throw new CancellationException(); mPagePercentMap.put(index, (float) receivedSize / contentLength); notifyPageDownload(index, contentLength, receivedSize, bytesRead); return null; }); - var contentLength = body.contentLength(); - body.close(); - response.close(); - if (received == -1) { - error = GetText.getString(R.string.error_write_failed); - response.close(); + // Check Stopped + if (mStoped) { + error = "Interrupted"; break; } - // check download size - if (contentLength >= 0) { - if (received < contentLength) { - Log.e(TAG, "Can't download all of image data"); - error = "Incomplete"; - forceHtml = true; - continue; - } else if (received > contentLength) { - Log.w(TAG, "Received data is more than contentLength"); - } + if (!success) { + Log.e(TAG, "Can't download all of image data"); + error = "Incomplete"; + forceHtml = true; + continue; } if (mSpiderDen.checkPlainText(index)) {