From 02855b68e6ce500a48eb93b9210b8f10376400a0 Mon Sep 17 00:00:00 2001 From: tarsin Date: Fri, 28 Jul 2023 17:24:42 +0800 Subject: [PATCH] Initial let parser use bytebuffer --- .../com/hippo/ehviewer/client/EhEngine.kt | 26 ++++++++++++++++++- .../ehviewer/client/parser/TorrentParser.kt | 6 +++-- .../com/hippo/ehviewer/cronet/CronetKt.kt | 21 ++++++++++----- app/src/main/rust/src/lib.rs | 18 ++++++++++++- app/src/main/rust/src/parser/torrent.rs | 11 ++++---- 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt index 81ebd1505..f741f17d4 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.kt @@ -45,10 +45,14 @@ import com.hippo.ehviewer.client.parser.TorrentParser import com.hippo.ehviewer.client.parser.TorrentResult import com.hippo.ehviewer.client.parser.VoteCommentResult import com.hippo.ehviewer.client.parser.VoteTagParser +import com.hippo.ehviewer.cronet.awaitBodyFully +import com.hippo.ehviewer.cronet.cronetRequest +import com.hippo.ehviewer.cronet.execute import com.hippo.ehviewer.dailycheck.showEventNotification import com.hippo.ehviewer.dailycheck.today import com.hippo.ehviewer.util.AppConfig import com.hippo.ehviewer.util.StatusCodeException +import io.ktor.utils.io.pool.DirectByteBufferPool import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.serialization.json.add @@ -65,6 +69,7 @@ import okhttp3.RequestBody.Companion.asRequestBody import org.jsoup.Jsoup import splitties.init.appCtx import java.io.File +import java.nio.ByteBuffer import kotlin.math.ceil private const val TAG = "EhEngine" @@ -124,6 +129,25 @@ private fun rethrowExactly(code: Int, headers: Headers, body: String, e: Throwab throw e } +val httpContentPool = DirectByteBufferPool(16, 16384) + +suspend inline fun fetchCompat(url: String, referer: String? = null, crossinline parser: (ByteBuffer) -> T): T { + val ret: T + cronetRequest(url, referer).execute { + val buffer = httpContentPool.borrow() + try { + awaitBodyFully { + buffer.put(it) + } + buffer.flip() + ret = parser(buffer) + } finally { + httpContentPool.recycle(buffer) + } + } + return ret +} + @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") private suspend inline fun Request.executeAndParsingWith(block: suspend String.() -> T): T { Log.d(TAG, url.toString()) @@ -151,7 +175,7 @@ object EhEngine { suspend fun getTorrentList(url: String, gid: Long, token: String?): TorrentResult { val referer = EhUrl.getGalleryDetailUrl(gid, token) - return ehRequest(url, referer).executeAndParsingWith(TorrentParser::parse) + return fetchCompat(url, referer, TorrentParser::parse) } suspend fun getArchiveList(url: String, gid: Long, token: String?) = ehRequest(url, EhUrl.getGalleryDetailUrl(gid, token)) diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt b/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt index 03d3b9c25..f4e880980 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/TorrentParser.kt @@ -1,7 +1,9 @@ package com.hippo.ehviewer.client.parser +import java.nio.ByteBuffer + object TorrentParser { - fun parse(body: String) = parseTorrent(body) + fun parse(body: ByteBuffer) = parseTorrent(body) } class Torrent( @@ -18,4 +20,4 @@ fun Torrent.format() = "[$posted] $name [$size] [↑$seeds ↓$peers ✓$downloa typealias TorrentResult = ArrayList -private external fun parseTorrent(body: String): ArrayList +private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): ArrayList diff --git a/app/src/main/java/com/hippo/ehviewer/cronet/CronetKt.kt b/app/src/main/java/com/hippo/ehviewer/cronet/CronetKt.kt index 38e7b82f4..6703a0805 100644 --- a/app/src/main/java/com/hippo/ehviewer/cronet/CronetKt.kt +++ b/app/src/main/java/com/hippo/ehviewer/cronet/CronetKt.kt @@ -19,6 +19,8 @@ import org.chromium.net.UrlResponseInfo import splitties.init.appCtx import java.nio.ByteBuffer import java.nio.channels.FileChannel +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -72,7 +74,7 @@ class CronetRequest { } } -inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.Builder.() -> Unit) = CronetRequest().apply { +inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.Builder.() -> Unit = {}) = CronetRequest().apply { request = cronetHttpClient.newUrlRequestBuilder(url, callback, cronetHttpClientExecutor).apply { addHeader("Cookie", EhCookieStore.getCookieHeader(url.toHttpUrl())) addHeader("User-Agent", CHROME_USER_AGENT) @@ -104,11 +106,16 @@ suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline li listener(bytes) } -suspend inline fun CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> Unit) = coroutineScope { - onResponse = { launch { callback(it) } } - suspendCancellableCoroutine { cont -> - cont.invokeOnCancellation { request.cancel() } - daemonCont = cont - request.start() +suspend inline fun CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> Unit): Boolean { + contract { + callsInPlace(callback, InvocationKind.EXACTLY_ONCE) + } + return coroutineScope { + onResponse = { launch { callback(it) } } + suspendCancellableCoroutine { cont -> + cont.invokeOnCancellation { request.cancel() } + daemonCont = cont + request.start() + } } } diff --git a/app/src/main/rust/src/lib.rs b/app/src/main/rust/src/lib.rs index 5dae05af2..8216c79a7 100644 --- a/app/src/main/rust/src/lib.rs +++ b/app/src/main/rust/src/lib.rs @@ -13,11 +13,13 @@ extern crate regex_lite; extern crate tl; use android_logger::Config; -use jnix::jni::objects::JString; +use jnix::jni::objects::{JByteBuffer, JString}; use jnix::jni::sys::{jint, JavaVM, JNI_VERSION_1_6}; use jnix::JnixEnv; use log::LevelFilter; use std::ffi::c_void; +use std::ptr::slice_from_raw_parts; +use std::str::from_utf8; use tl::{Bytes, Node, NodeHandle, Parser, VDom}; #[macro_export] @@ -70,6 +72,20 @@ where f(&dom, parser, env) } +fn parse_bytebuffer(env: &mut JnixEnv, str: JByteBuffer, limit: jint, mut f: F) -> Option +where + F: FnMut(&VDom, &Parser, &JnixEnv) -> Option, +{ + let ptr = env.get_direct_buffer_address(str).ok()?; + let html = unsafe { + let buff = slice_from_raw_parts(ptr, limit as usize); + from_utf8(&*buff).ok()? + }; + let dom = tl::parse(html, tl::ParserOptions::default()).ok()?; + let parser = dom.parser(); + f(&dom, parser, env) +} + fn get_node_handle_attr<'a>( node: &NodeHandle, parser: &'a Parser, diff --git a/app/src/main/rust/src/parser/torrent.rs b/app/src/main/rust/src/parser/torrent.rs index b7885be00..0fa140f33 100644 --- a/app/src/main/rust/src/parser/torrent.rs +++ b/app/src/main/rust/src/parser/torrent.rs @@ -1,12 +1,13 @@ use catch_panic::catch_panic; use jni_fn::jni_fn; -use jnix::jni::objects::{JClass, JString}; -use jnix::jni::sys::jobject; +use jnix::jni::objects::{JByteBuffer, JClass}; +use jnix::jni::sys::{jint, jobject}; use jnix::jni::JNIEnv; use jnix::{IntoJava, JnixEnv}; use jnix_macros::IntoJava; +use parse_bytebuffer; use quick_xml::escape::unescape; -use {parse_jni_string, regex}; +use regex; #[derive(Default, IntoJava)] #[jnix(package = "com.hippo.ehviewer.client.parser")] @@ -24,9 +25,9 @@ pub struct Torrent { #[catch_panic(default = "std::ptr::null_mut()")] #[allow(non_snake_case)] #[jni_fn("com.hippo.ehviewer.client.parser.TorrentParserKt")] -pub fn parseTorrent(env: JNIEnv, _class: JClass, input: JString) -> jobject { +pub fn parseTorrent(env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jobject { let mut env = JnixEnv { env }; - parse_jni_string(&mut env, &input, |dom, parser, _env| { + parse_bytebuffer(&mut env, buffer, limit, |dom, parser, _env| { Some(dom.query_selector("table")?.filter_map(|e| { let html = e.get(parser)?.inner_html(parser); let grp = regex!(" ([0-9-]+) [0-9:]+[\\s\\S]+ ([0-9.]+ [KMGT]B)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+([^<]+)[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)").captures(&html)?;