Group rust ffi code (#1893)

* Group rust ffi code

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Fix

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Fix

Signed-off-by: Dude so hot <djohn@fbi.gov>

* Refine

Signed-off-by: Dude so hot <djohn@fbi.gov>

---------

Signed-off-by: Dude so hot <djohn@fbi.gov>
Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
renovate[bot]andDude so hot authored and GitHub committed 2024-11-05 09:39:03 +00:00
1 parent a566393a89
commit 45a5bae8b2
16 files changed
+246 -241

No files matched your search

+3 -1
View File
@@ -111,7 +111,9 @@ FetchContent_MakeAvailable(Corrosion)
corrosion_import_crate(MANIFEST_PATH ../rust/Cargo.toml LOCKED)
corrosion_add_target_local_rustflags(ehviewer_rust "-Clink-args=-Wl,-z,max-page-size=16384")
if (ANDROID_NATIVE_API_LEVEL GREATER_EQUAL 26)
corrosion_set_features(ehviewer_rust FEATURES api-level-26)
corrosion_set_features(ehviewer_rust FEATURES android-26)
else ()
corrosion_set_features(ehviewer_rust FEATURES android)
endif ()
# Build and link our app's native lib
+6 -4
View File
@@ -7,12 +7,14 @@ edition = "2021"
crate-type = ["cdylib"]
[features]
api-level-26 = ["ndk/api-level-26"]
jvm = ["dep:jni", "dep:jni_fn"]
android = ["dep:ndk", "jvm", "ndk/bitmap"]
android-26 = ["android", "ndk/api-level-26"]
[dependencies]
regex-lite = "0.1"
jni = "0.21"
jni_fn = "0.1"
jni = { version = "0.21", optional = true }
jni_fn = { version = "0.1", optional = true }
quick-xml = { default-features = false, version = "0.37" }
tl = "0.7"
log = "0.4"
@@ -22,7 +24,7 @@ serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0"
image = { default-features = false, version = "0.25" }
rxing = { default-features = false, version = "0.6" }
ndk = { version = "0.9", features = ["bitmap"] }
ndk = { version = "0.9", optional = true }
[profile.release]
panic = "abort"
+3
View File
@@ -0,0 +1,3 @@
mod android;
mod android_o;
mod jvm;
+55
View File
@@ -0,0 +1,55 @@
#![cfg(feature = "android")]
use super::jvm::jni_throwing;
use crate::img::border::detect_border;
use crate::img::qr_code::detect_image_ad;
use anyhow::{Context, Result};
use image::{ImageBuffer, Rgba};
use jni::objects::JClass;
use jni::sys::jboolean;
use jni::sys::{jintArray, jobject};
use jni::JNIEnv;
use jni_fn::jni_fn;
use ndk::bitmap::Bitmap;
use std::ptr::slice_from_raw_parts;
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.image.ImageKt")]
pub fn detectBorder(mut env: JNIEnv, _class: JClass, object: jobject) -> jintArray {
jni_throwing(&mut env, |env| {
let slice = with_bitmap_content(env, object, |img| Ok(detect_border(&img)))?;
let array = env.new_int_array(4)?;
env.set_int_array_region(&array, 0, &slice.context("Image too small!")?)?;
Ok(array.into_raw())
})
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.image.ImageKt")]
pub fn hasQrCode(mut env: JNIEnv, _class: JClass, object: jobject) -> jboolean {
jni_throwing(&mut env, |env| {
with_bitmap_content(env, object, |image| Ok(detect_image_ad(image) as jboolean))
})
}
pub fn with_bitmap_content<F, R>(env: &mut JNIEnv, bitmap: jobject, f: F) -> Result<R>
where
F: FnOnce(ImageBuffer<Rgba<u8>, &[u8]>) -> Result<R>,
{
// SAFETY: kotlin caller must ensure bitmap is valid.
let handle = unsafe { Bitmap::from_jni(env.get_raw(), bitmap) };
let info = handle.info()?;
let (width, height) = (info.width(), info.height());
let ptr = handle.lock_pixels()? as *const u8;
// SAFETY: maybe unsafe if bitmap buffer not RGBA8888 format.
let buffer = unsafe { &*slice_from_raw_parts(ptr, (width * height * 4) as usize) };
let image = ImageBuffer::from_raw(width, height, buffer);
let result = image.context("Image buffer not RGBA8888!!!").and_then(f);
handle.unlock_pixels()?;
result
}
@@ -1,6 +1,7 @@
#![cfg(feature = "api-level-26")]
#![cfg(feature = "android-26")]
use crate::{jni_throwing, with_bitmap_content};
use super::android::with_bitmap_content;
use super::jvm::jni_throwing;
use anyhow::Context;
use image::{GenericImage, GenericImageView, ImageBuffer};
use jni::objects::JClass;
+143
View File
@@ -0,0 +1,143 @@
#![cfg(feature = "jvm")]
use crate::parser::config::parse_fav_cat;
use crate::parser::fav::parse_fav;
use crate::parser::home::parse_limit;
use crate::parser::list::parse_info_list;
use crate::parser::torrent::parse_torrent_list;
use crate::EhError;
use android_logger::Config;
use anyhow::{ensure, Context, Result};
use jni::objects::{JByteBuffer, JClass};
use jni::sys::{jboolean, jint, jobject, JNI_VERSION_1_6};
use jni::{JNIEnv, JavaVM};
use jni_fn::jni_fn;
use log::LevelFilter;
use serde::Serialize;
use std::ffi::c_void;
use std::io::Cursor;
use std::ptr::slice_from_raw_parts_mut;
use std::str::from_utf8_unchecked;
use tl::{ParserOptions, VDom};
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.FavoritesParserKt")]
pub fn parseFav(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, input, limit, |dom, html| {
parse_fav(dom, dom.parser(), html)
})
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.HomeParserKt")]
pub fn parseLimit(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, input, limit, |dom, _| {
parse_limit(dom, dom.parser()).context("Can't parse Limit")
})
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.GalleryListParserKt")]
pub fn parseGalleryInfoList(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |dom, str| {
parse_info_list(dom, dom.parser(), str)
})
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.TorrentParserKt")]
pub fn parseTorrent(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |dom, _| {
parse_torrent_list(dom, dom.parser())
})
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.UserConfigParser")]
pub fn parseFavCat(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |_, body| Ok(parse_fav_cat(body)))
}
fn deref_mut_direct_bytebuffer<'local>(
env: &JNIEnv,
buffer: JByteBuffer<'local>,
) -> Result<&'local mut [u8]> {
let ptr = env.get_direct_buffer_address(&buffer)?;
let cap = env.get_direct_buffer_capacity(&buffer)?;
// SAFETY: jni contract, buffer must alive through native call.
Ok(unsafe { &mut *slice_from_raw_parts_mut(ptr, cap) })
}
pub trait ThrowingHasDefault {
fn default() -> Self;
}
impl ThrowingHasDefault for jboolean {
fn default() -> Self {
0 as jboolean
}
}
impl ThrowingHasDefault for jobject {
fn default() -> Self {
0 as jobject
}
}
impl ThrowingHasDefault for i32 {
fn default() -> Self {
0
}
}
impl ThrowingHasDefault for () {
fn default() -> Self {}
}
pub fn jni_throwing<F, R>(env: &mut JNIEnv, f: F) -> R
where
F: FnOnce(&mut JNIEnv) -> Result<R>,
R: ThrowingHasDefault,
{
match f(env) {
Ok(value) => value,
Err(err) => {
let msg = format!("{err}");
env.throw_new("java/lang/RuntimeException", msg).ok();
R::default()
}
}
}
pub fn parse_marshal_inplace<F, R>(env: &mut JNIEnv, str: JByteBuffer, limit: jint, f: F) -> i32
where
F: Fn(&VDom, &str) -> Result<R>,
R: Serialize,
{
jni_throwing(env, |env| {
let buffer = deref_mut_direct_bytebuffer(env, str)?;
let value = {
// SAFETY: ktor client ensure html content is valid utf-8.
let html = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) };
let dom = tl::parse(html, ParserOptions::default())?;
ensure!(dom.version().is_some(), EhError::Error(html.to_string()));
f(&dom, html)?
};
let mut cursor = Cursor::new(buffer);
serde_cbor::to_writer(&mut cursor, &value)?;
Ok(cursor.position() as i32)
})
}
#[no_mangle]
pub extern "system" fn JNI_OnLoad(_: JavaVM, _: *mut c_void) -> jint {
android_logger::init_once(Config::default().with_max_level(LevelFilter::Debug));
JNI_VERSION_1_6
}
+2 -3
View File
@@ -1,3 +1,2 @@
mod border;
mod copy_ahb;
mod qr_code;
pub mod border;
pub mod qr_code;
+2 -19
View File
@@ -1,10 +1,4 @@
use crate::{jni_throwing, with_bitmap_content};
use anyhow::Context;
use image::{ImageBuffer, Luma, Pixel, Rgba};
use jni::objects::JClass;
use jni::sys::{jintArray, jobject};
use jni::JNIEnv;
use jni_fn::jni_fn;
/** A line will be considered as having content if 0.25% of it is filled. */
static FILLED_RATIO_LIMIT: f32 = 0.0025;
@@ -72,7 +66,8 @@ fn detect_border_lines<'pixel>(
}
}
fn detect_border(image: &ImageBuffer<Rgba<u8>, &[u8]>) -> Option<[i32; 4]> {
#[allow(dead_code)]
pub fn detect_border(image: &ImageBuffer<Rgba<u8>, &[u8]>) -> Option<[i32; 4]> {
let (w, h) = image.dimensions();
let top = detect_border_lines(image.rows(), w);
let bottom = detect_border_lines(image.rows().rev(), w);
@@ -81,15 +76,3 @@ fn detect_border(image: &ImageBuffer<Rgba<u8>, &[u8]>) -> Option<[i32; 4]> {
let right = detect_border_lines(iter.rev(), h);
Some([left, top, w as i32 - right, h as i32 - bottom])
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.image.ImageKt")]
pub fn detectBorder(mut env: JNIEnv, _class: JClass, object: jobject) -> jintArray {
jni_throwing(&mut env, |env| {
let slice = with_bitmap_content(env, object, |img| Ok(detect_border(&img)))?;
let array = env.new_int_array(4)?;
env.set_int_array_region(&array, 0, &slice.context("Image too small!")?)?;
Ok(array.into_raw())
})
}
+2 -15
View File
@@ -1,16 +1,12 @@
use crate::{jni_throwing, with_bitmap_content};
use image::{ImageBuffer, Rgba};
use jni::objects::JClass;
use jni::sys::{jboolean, jobject};
use jni::JNIEnv;
use jni_fn::jni_fn;
use rxing::common::HybridBinarizer;
use rxing::qrcode::detector::FinderPatternFinder;
use rxing::{BinaryBitmap, DecodingHintDictionary, Point, RGBLuminanceSource};
use std::f32::consts::FRAC_1_SQRT_2;
use std::ptr::slice_from_raw_parts;
fn detect_image_ad(image: ImageBuffer<Rgba<u8>, &[u8]>) -> bool {
#[allow(dead_code)]
pub fn detect_image_ad(image: ImageBuffer<Rgba<u8>, &[u8]>) -> bool {
let (width, height) = (image.width() as usize, image.height() as usize);
let buffer = image.into_raw();
let pixels = unsafe { &*slice_from_raw_parts(buffer.as_ptr() as *const u32, buffer.len()) };
@@ -30,12 +26,3 @@ fn detect_image_ad(image: ImageBuffer<Rgba<u8>, &[u8]>) -> bool {
Err(_) => false,
}
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.image.ImageKt")]
pub fn hasQrCode(mut env: JNIEnv, _class: JClass, object: jobject) -> jboolean {
jni_throwing(&mut env, |env| {
with_bitmap_content(env, object, |image| Ok(detect_image_ad(image) as jboolean))
})
}
+1 -113
View File
@@ -1,21 +1,8 @@
mod ffi;
mod img;
mod parser;
use android_logger::Config;
use anyhow::{ensure, Context, Result};
use image::{ImageBuffer, Rgba};
use jni::objects::JByteBuffer;
use jni::sys::{jboolean, jint, jobject, JavaVM, JNI_VERSION_1_6};
use jni::JNIEnv;
use log::LevelFilter;
use ndk::bitmap::Bitmap;
use serde::Serialize;
use std::ffi::c_void;
use std::fmt::{Debug, Display, Formatter};
use std::io::Cursor;
use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut};
use std::str::from_utf8_unchecked;
use tl::ParserOptions;
use tl::{Bytes, Node, NodeHandle, Parser, VDom};
#[macro_export]
@@ -58,43 +45,6 @@ where
handle.get(parser)
}
fn deref_mut_direct_bytebuffer<'local>(
env: &JNIEnv,
buffer: JByteBuffer<'local>,
) -> Result<&'local mut [u8]> {
let ptr = env.get_direct_buffer_address(&buffer)?;
let cap = env.get_direct_buffer_capacity(&buffer)?;
// SAFETY: jni contract, buffer must alive through native call.
Ok(unsafe { &mut *slice_from_raw_parts_mut(ptr, cap) })
}
trait ThrowingHasDefault {
fn default() -> Self;
}
impl ThrowingHasDefault for jboolean {
fn default() -> Self {
0 as jboolean
}
}
impl ThrowingHasDefault for jobject {
fn default() -> Self {
0 as jobject
}
}
impl ThrowingHasDefault for i32 {
fn default() -> Self {
0
}
}
impl ThrowingHasDefault for () {
fn default() -> Self {}
}
#[derive(Debug)]
enum EhError {
NoHits,
@@ -115,42 +65,6 @@ impl Display for EhError {
}
}
fn jni_throwing<F, R>(env: &mut JNIEnv, f: F) -> R
where
F: FnOnce(&mut JNIEnv) -> Result<R>,
R: ThrowingHasDefault,
{
match f(env) {
Ok(value) => value,
Err(err) => {
let msg = format!("{err}");
env.throw_new("java/lang/RuntimeException", msg).ok();
R::default()
}
}
}
fn parse_marshal_inplace<F, R>(env: &mut JNIEnv, str: JByteBuffer, limit: jint, f: F) -> i32
where
F: Fn(&VDom, &str) -> Result<R>,
R: Serialize,
{
jni_throwing(env, |env| {
let buffer = deref_mut_direct_bytebuffer(env, str)?;
let value = {
// SAFETY: ktor client ensure html content is valid utf-8.
let html = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) };
let dom = tl::parse(html, ParserOptions::default())?;
ensure!(dom.version().is_some(), EhError::Error(html.to_string()));
f(&dom, html)?
};
let mut cursor = Cursor::new(buffer);
serde_cbor::to_writer(&mut cursor, &value)?;
Ok(cursor.position() as i32)
})
}
fn get_node_handle_attr<'a>(
node: &NodeHandle,
parser: &'a Parser,
@@ -173,29 +87,3 @@ fn query_childs_first_match_attr<'a>(
let mut iter = node.as_tag()?.query_selector(parser, &selector)?;
get_node_handle_attr(&iter.next()?, parser, attr)
}
fn with_bitmap_content<F, R>(env: &mut JNIEnv, bitmap: jobject, f: F) -> Result<R>
where
F: FnOnce(ImageBuffer<Rgba<u8>, &[u8]>) -> Result<R>,
{
// SAFETY: kotlin caller must ensure bitmap is valid.
let handle = unsafe { Bitmap::from_jni(env.get_raw(), bitmap) };
let info = handle.info()?;
let (width, height) = (info.width(), info.height());
let ptr = handle.lock_pixels()? as *const u8;
// SAFETY: maybe unsafe if bitmap buffer not RGBA8888 format.
let buffer = unsafe { &*slice_from_raw_parts(ptr, (width * height * 4) as usize) };
let image = ImageBuffer::from_raw(width, height, buffer);
let result = image.context("Image buffer not RGBA8888!!!").and_then(f);
handle.unlock_pixels()?;
result
}
#[no_mangle]
pub extern "system" fn JNI_OnLoad(_: JavaVM, _: *mut c_void) -> jint {
android_logger::init_once(Config::default().with_max_level(LevelFilter::Debug));
JNI_VERSION_1_6
}
+5 -5
View File
@@ -1,5 +1,5 @@
mod config;
mod fav;
mod home;
mod list;
mod torrent;
pub mod config;
pub mod fav;
pub mod home;
pub mod list;
pub mod torrent;
+7 -16
View File
@@ -1,19 +1,10 @@
use crate::{parse_marshal_inplace, regex};
use jni::objects::{JByteBuffer, JClass};
use jni::sys::jint;
use jni::JNIEnv;
use jni_fn::jni_fn;
use crate::regex;
use quick_xml::escape::unescape;
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.UserConfigParser")]
pub fn parseFavCat(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |_, body| {
let cat = regex!("<input type=\"text\" name=\"favorite_\\d\" value=\"([^\"]+)\"")
.captures_iter(body)
.filter_map(|c| unescape(&c[1]).map(|s| s.to_string()).ok())
.collect::<Vec<_>>();
Ok(cat)
})
#[allow(dead_code)]
pub fn parse_fav_cat(html: &str) -> Vec<String> {
regex!("<input type=\"text\" name=\"favorite_\\d\" value=\"([^\"]+)\"")
.captures_iter(html)
.filter_map(|c| unescape(&c[1]).map(|s| s.to_string()).ok())
.collect()
}
+4 -16
View File
@@ -1,10 +1,6 @@
use crate::parser::list::{parse_info_list, GalleryListResult};
use crate::{parse_marshal_inplace, EhError};
use crate::EhError;
use anyhow::{bail, Result};
use jni::objects::{JByteBuffer, JClass};
use jni::sys::jint;
use jni::JNIEnv;
use jni_fn::jni_fn;
use quick_xml::escape::unescape;
use serde::Serialize;
use tl::Parser;
@@ -12,13 +8,14 @@ use tl::VDom;
#[derive(Serialize)]
#[allow(non_snake_case)]
struct FavResult {
pub struct FavResult {
catArray: Vec<String>,
countArray: Vec<i32>,
galleryListResult: GalleryListResult,
}
fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Result<FavResult> {
#[allow(dead_code)]
pub fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Result<FavResult> {
if html.contains("This page requires you to log on.</p>") {
bail!(EhError::NeedLogin)
}
@@ -53,12 +50,3 @@ fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Result<FavResult> {
bail!("Illegal fav cat count!")
}
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.FavoritesParserKt")]
pub fn parseFav(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, input, limit, |dom, html| {
parse_fav(dom, dom.parser(), html)
})
}
+4 -17
View File
@@ -1,22 +1,18 @@
use crate::{get_vdom_first_element_by_class_name, parse_marshal_inplace};
use anyhow::Context;
use jni::objects::{JByteBuffer, JClass};
use jni::sys::jint;
use jni::JNIEnv;
use jni_fn::jni_fn;
use crate::get_vdom_first_element_by_class_name;
use serde::Serialize;
use tl::Parser;
use tl::VDom;
#[allow(non_snake_case)]
#[derive(Serialize)]
struct Limits {
pub struct Limits {
current: i32,
maximum: i32,
resetCost: i32,
}
fn parse_limit(dom: &VDom, parser: &Parser) -> Option<Limits> {
#[allow(dead_code)]
pub fn parse_limit(dom: &VDom, parser: &Parser) -> Option<Limits> {
let limit_box = get_vdom_first_element_by_class_name(dom, "homebox")?.as_tag()?;
let vec = limit_box
.query_selector(parser, "strong")?
@@ -46,12 +42,3 @@ fn parse_limit(dom: &VDom, parser: &Parser) -> Option<Limits> {
_ => None,
}
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.HomeParserKt")]
pub fn parseLimit(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, input, limit, |dom, _| {
parse_limit(dom, dom.parser()).context("Can't parse Limit")
})
}
+2 -14
View File
@@ -1,13 +1,9 @@
use crate::EhError;
use crate::{get_element_by_id, get_vdom_first_element_by_class_name};
use crate::{get_first_element_by_class_name, query_childs_first_match_attr};
use crate::{get_node_attr, get_node_handle_attr, regex};
use crate::{parse_marshal_inplace, EhError};
use crate::{EHGT_PREFIX, EX_PREFIX};
use anyhow::{bail, Context, Result};
use jni::objects::{JByteBuffer, JClass};
use jni::sys::jint;
use jni::JNIEnv;
use jni_fn::jni_fn;
use quick_xml::escape::unescape;
use serde::Serialize;
use std::borrow::Cow;
@@ -197,6 +193,7 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option<BaseGalleryInfo> {
})
}
#[allow(dead_code)]
pub fn parse_info_list(dom: &VDom, parser: &Parser, str: &str) -> Result<GalleryListResult> {
if str.contains("<p>You do not have any watched tags") {
bail!(EhError::NoWatched)
@@ -241,12 +238,3 @@ pub fn parse_info_list(dom: &VDom, parser: &Parser, str: &str) -> Result<Gallery
};
f().context("No content")
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.GalleryListParserKt")]
pub fn parseGalleryInfoList(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |dom, str| {
parse_info_list(dom, dom.parser(), str)
})
}
+4 -16
View File
@@ -1,15 +1,11 @@
use crate::{parse_marshal_inplace, regex};
use crate::regex;
use anyhow::{Context, Result};
use jni::objects::{JByteBuffer, JClass};
use jni::sys::jint;
use jni::JNIEnv;
use jni_fn::jni_fn;
use quick_xml::escape::unescape;
use serde::Serialize;
use tl::{Parser, VDom};
#[derive(Serialize)]
struct Torrent {
pub struct Torrent {
outdated: bool,
posted: String,
size: String,
@@ -21,7 +17,8 @@ struct Torrent {
name: String,
}
fn parse_torrent_list(dom: &VDom, parser: &Parser) -> Result<Vec<Torrent>> {
#[allow(dead_code)]
pub fn parse_torrent_list(dom: &VDom, parser: &Parser) -> Result<Vec<Torrent>> {
let list = dom.query_selector("table").context("No Table")?.filter_map(|e| {
let html = e.get(parser)?.inner_html(parser);
if html.contains("Expunged") {
@@ -44,12 +41,3 @@ fn parse_torrent_list(dom: &VDom, parser: &Parser) -> Result<Vec<Torrent>> {
}).collect::<Vec<Torrent>>();
Ok(list)
}
#[no_mangle]
#[allow(non_snake_case)]
#[jni_fn("com.hippo.ehviewer.client.parser.TorrentParserKt")]
pub fn parseTorrent(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint {
parse_marshal_inplace(&mut env, buffer, limit, |dom, _| {
parse_torrent_list(dom, dom.parser())
})
}