Migrate image downloading to ktor

This commit is contained in:
tarsin committed 2023-02-04 12:20:41 +08:00
1 parent 6117ea755f
commit 057ba0c6e1
3 files changed
+66 -69

No files matched your search

@@ -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
@@ -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 {
@@ -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)) {