Refactor Rust parser, decouple with JNI for better further KMP support (#568)
* Use serde & kotlinx serialization to marshal jni data * Introduce unmarshalParsingAs * Comments * Introduce parse_marshal_inplace * Convert parseLimit * Aha, convert parseGalleryInfoList * Drop cache_unwind * Unwind manually * Fixes * Drop jnix, update jni * Data class * Cleanups * Cleanups * Optimize a copy --------- Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
1 parent
5a9ab6c4eb
commit
6e3471ba1b
29 files changed
+308
-1231
No files matched your search
@@ -31,6 +31,7 @@ import com.hippo.ehviewer.client.exception.NotLoggedInException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import com.hippo.ehviewer.client.parser.ArchiveParser
|
||||
import com.hippo.ehviewer.client.parser.EventPaneParser
|
||||
import com.hippo.ehviewer.client.parser.FavParserResult
|
||||
import com.hippo.ehviewer.client.parser.FavoritesParser
|
||||
import com.hippo.ehviewer.client.parser.ForumsParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryApiParser
|
||||
@@ -324,7 +325,7 @@ object EhEngine {
|
||||
}
|
||||
}.fetchUsingAsText(HomeParser::parseResetLimits)
|
||||
|
||||
suspend fun modifyFavorites(gidArray: LongArray, srcCat: Int, dstCat: Int): FavoritesParser.Result {
|
||||
suspend fun modifyFavorites(gidArray: LongArray, srcCat: Int, dstCat: Int): FavParserResult {
|
||||
val url = ehUrl(EhUrl.FAV_PATH) {
|
||||
if (FavListUrlBuilder.isValidFavCat(srcCat)) addQueryParameter("favcat", srcCat.toString())
|
||||
}.buildString()
|
||||
|
||||
@@ -16,17 +16,18 @@
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Parcelize
|
||||
@Serializable
|
||||
@Entity(tableName = "GALLERIES")
|
||||
data class BaseGalleryInfo @Keep constructor(
|
||||
data class BaseGalleryInfo(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
override var gid: Long = 0,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.parseAs
|
||||
import java.nio.ByteBuffer
|
||||
import okio.Buffer
|
||||
import okio.Source
|
||||
import okio.Timeout
|
||||
import okio.buffer
|
||||
|
||||
fun ByteBuffer.asSource() = object : Source {
|
||||
private val buffer = this@asSource.slice()
|
||||
private val len = buffer.capacity()
|
||||
|
||||
override fun close() = Unit
|
||||
|
||||
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||
if (buffer.position() == len) return -1
|
||||
val pos = buffer.position()
|
||||
val newLimit = (pos + byteCount).toInt().coerceAtMost(len)
|
||||
buffer.limit(newLimit)
|
||||
return sink.write(buffer).toLong()
|
||||
}
|
||||
|
||||
override fun timeout() = Timeout.NONE
|
||||
}
|
||||
|
||||
inline fun <reified T> unmarshalParsingAs(body: ByteBuffer, parser: (ByteBuffer, Int) -> Int): T {
|
||||
// We want to use cbor as it have smaller output size
|
||||
// But we would rather reuse allocated ByteArrays (i.e. decode with okio Buffer)
|
||||
val jsonBytes = parser(body, body.limit())
|
||||
body.clear()
|
||||
body.limit(jsonBytes)
|
||||
return body.asSource().buffer().use { it.parseAs<T>() }
|
||||
}
|
||||
@@ -3,30 +3,29 @@ package com.hippo.ehviewer.client.parser
|
||||
import com.hippo.ehviewer.client.exception.NotLoggedInException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
@Serializable
|
||||
data class FavParserResult(
|
||||
val catArray: List<String>,
|
||||
val countArray: List<Int>,
|
||||
val galleryListResult: GalleryListResult,
|
||||
) {
|
||||
val prev = galleryListResult.prev
|
||||
val next = galleryListResult.next
|
||||
val galleryInfoList = galleryListResult.galleryInfoList
|
||||
}
|
||||
|
||||
object FavoritesParser {
|
||||
suspend fun parse(body: ByteBuffer): Result {
|
||||
val catArray = arrayOfNulls<String>(10)
|
||||
val countArray = runSuspendCatching {
|
||||
parseFav(body, favCat = catArray).also { check(it.isNotEmpty()) }
|
||||
fun parse(body: ByteBuffer): FavParserResult {
|
||||
return runSuspendCatching {
|
||||
unmarshalParsingAs<FavParserResult>(body, ::parseFav)
|
||||
}.getOrElse {
|
||||
if (it is RuntimeException && it.message == "Not logged in!") throw NotLoggedInException()
|
||||
throw ParseException("Parse favorites error", it)
|
||||
}
|
||||
val result = GalleryListParser.parse(body)
|
||||
return Result(catArray.requireNoNulls(), countArray, result)
|
||||
}
|
||||
|
||||
class Result(
|
||||
val catArray: Array<String>,
|
||||
val countArray: IntArray,
|
||||
galleryListResult: GalleryListResult,
|
||||
) {
|
||||
val prev = galleryListResult.prev
|
||||
val next = galleryListResult.next
|
||||
val galleryInfoList = galleryListResult.galleryInfoList
|
||||
}
|
||||
}
|
||||
|
||||
private external fun parseFav(body: ByteBuffer, limit: Int = body.limit(), favCat: Array<String?>): IntArray
|
||||
private external fun parseFav(body: ByteBuffer, limit: Int = body.limit()): Int
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
@@ -9,11 +8,12 @@ import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
object GalleryListParser {
|
||||
suspend fun parse(body: ByteBuffer) = runSuspendCatching {
|
||||
parseGalleryInfoList(body)
|
||||
unmarshalParsingAs<GalleryListResult>(body, ::parseGalleryInfoList)
|
||||
}.onFailure {
|
||||
if (it is RuntimeException) {
|
||||
if (it.message?.startsWith("Your IP address") == true) {
|
||||
@@ -39,10 +39,11 @@ object GalleryListParser {
|
||||
val emptyResult = GalleryListResult(null, null, arrayListOf())
|
||||
}
|
||||
|
||||
class GalleryListResult @Keep constructor(
|
||||
@Serializable
|
||||
data class GalleryListResult(
|
||||
val prev: String?,
|
||||
val next: String?,
|
||||
val galleryInfoList: ArrayList<BaseGalleryInfo>,
|
||||
)
|
||||
|
||||
private external fun parseGalleryInfoList(body: ByteBuffer, size: Int = body.limit()): GalleryListResult
|
||||
private external fun parseGalleryInfoList(body: ByteBuffer, size: Int = body.limit()): Int
|
||||
@@ -1,18 +1,18 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import com.hippo.ehviewer.client.exception.InsufficientFundsException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
object HomeParser {
|
||||
private val PATTERN_FUNDS = Regex("Available: ([\\d,]+) Credits.*Available: ([\\d,]+) kGP", RegexOption.DOT_MATCHES_ALL)
|
||||
private const val INSUFFICIENT_FUNDS = "Insufficient funds."
|
||||
|
||||
fun parse(body: ByteBuffer) = runCatching {
|
||||
parseLimit(body)
|
||||
unmarshalParsingAs<Limits>(body, ::parseLimit)
|
||||
}.getOrElse { throw ParseException("Parse image limits error", it) }
|
||||
|
||||
fun parseResetLimits(body: String) {
|
||||
@@ -36,6 +36,7 @@ object HomeParser {
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Limits @Keep constructor(val current: Int, val maximum: Int, val resetCost: Int) : Parcelable
|
||||
@Serializable
|
||||
data class Limits(val current: Int, val maximum: Int, val resetCost: Int) : Parcelable
|
||||
|
||||
private external fun parseLimit(body: ByteBuffer, limit: Int = body.limit()): Limits
|
||||
private external fun parseLimit(body: ByteBuffer, limit: Int = body.limit()): Int
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
object TorrentParser {
|
||||
fun parse(body: ByteBuffer) = runCatching {
|
||||
parseTorrent(body)
|
||||
}.getOrElse { throw ParseException("Can't parse torrent list", it) }
|
||||
unmarshalParsingAs<TorrentResult>(body, ::parseTorrent)
|
||||
}.getOrElse {
|
||||
throw ParseException("Can't parse torrent list", it)
|
||||
}
|
||||
}
|
||||
|
||||
class Torrent @Keep constructor(
|
||||
@Serializable
|
||||
data class Torrent(
|
||||
val posted: String,
|
||||
val size: String,
|
||||
val seeds: Int,
|
||||
@@ -24,4 +27,4 @@ fun Torrent.format() = "[$posted] $name [$size] [↑$seeds ↓$peers ✓$downloa
|
||||
|
||||
typealias TorrentResult = List<Torrent>
|
||||
|
||||
private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): ArrayList<Torrent>
|
||||
private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): Int
|
||||
@@ -152,8 +152,8 @@ fun FavouritesScreen(navigator: DestinationsNavigator) {
|
||||
}.onFailure {
|
||||
return@withIOContext LoadResult.Error(it)
|
||||
}.getOrThrow()
|
||||
Settings.favCat = r.catArray
|
||||
Settings.favCount = r.countArray
|
||||
Settings.favCat = r.catArray.toTypedArray()
|
||||
Settings.favCount = r.countArray.toIntArray()
|
||||
Settings.favCloudCount = r.countArray.sum()
|
||||
urlBuilder.jumpTo = null
|
||||
LoadResult.Page(r.galleryInfoList, r.prev, r.next)
|
||||
|
||||
@@ -8,10 +8,7 @@ crate_type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
regex-lite = { version = "0.1" }
|
||||
jnix = { path = "jnix" }
|
||||
jnix-macros = "0.4"
|
||||
catch_panic = { path = "catch_panic" }
|
||||
catch_panic_macros = { path = "catch_panic_macros" }
|
||||
jni = "0.21"
|
||||
jni_fn = "0.1.2"
|
||||
quick-xml = { default-features = false, version = "0.31" }
|
||||
tl = "0.7"
|
||||
@@ -19,6 +16,8 @@ log = "0.4"
|
||||
android_logger = { default-features = false, version = "0.13" }
|
||||
apply = "0.3"
|
||||
once_cell = { default-features = false, version = "1.19" }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "catch_panic"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Arc-blroth <45273859+Arc-blroth@users.noreply.github.com>",
|
||||
"Cayden Pocrass <caydenrp9@gmail.com>",
|
||||
"AdityaJain1030 <50159919+AdityaJain1030@users.noreply.github.com>"
|
||||
]
|
||||
readme = "../README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/HermitSocialClub/catch_panic"
|
||||
description = "A helper macro for safe Java-Rust interop that \"catches\" Rust panics and rethrows them as Java exceptions."
|
||||
keywords = ["ffi", "jni", "java"]
|
||||
categories = ["api-bindings"]
|
||||
|
||||
[dependencies]
|
||||
jni = "0.20"
|
||||
catch_panic_macros = { path = "../catch_panic_macros" }
|
||||
@@ -1,38 +0,0 @@
|
||||
//! Implementation of the default `#[catch_panic]` handler.
|
||||
|
||||
use std::any::Any;
|
||||
use std::panic::{catch_unwind, UnwindSafe};
|
||||
|
||||
use jni::JNIEnv;
|
||||
|
||||
/// The wrapper method that `#[catch_panic]` invokes to actually catch panics.
|
||||
/// This is an internal implementation detail - don't call this function directly!
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn __catch_panic<F, R, H>(env: JNIEnv, default: R, handler: H, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R + UnwindSafe,
|
||||
H: FnOnce(JNIEnv, Box<dyn Any + Send + 'static>),
|
||||
{
|
||||
match catch_unwind(f) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
handler(env, err);
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `#[catch_panic]`'s default panic handler. This
|
||||
/// will rethrow all caught panics as java `RuntimeException`s
|
||||
/// with the message passed to `panic!`.
|
||||
pub fn default_handler(env: JNIEnv, err: Box<dyn Any + Send + 'static>) {
|
||||
let msg = match err.downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match err.downcast_ref::<String>() {
|
||||
Some(s) => &s[..],
|
||||
None => "Box<dyn Any>",
|
||||
},
|
||||
};
|
||||
env.throw_new("java/lang/RuntimeException", msg).unwrap();
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//! # #\[catch_panic\]
|
||||
//!
|
||||
//! A helper macro for safe Java-Rust interop that "catches" Rust panics
|
||||
//! and rethrows them as Java exceptions.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! Attach `#[catch_panic]` to a JNI callback to have panics converted into `RuntimeException`s:
|
||||
//!
|
||||
//! ```
|
||||
//! # use jni::JNIEnv;
|
||||
//! # use catch_panic::catch_panic;
|
||||
//! #
|
||||
//! #[no_mangle]
|
||||
//! #[catch_panic]
|
||||
//! pub extern "C" fn Java_com_example_Example_panic(_env: JNIEnv) {
|
||||
//! panic!("everything is not fine");
|
||||
//! }
|
||||
//!
|
||||
//! # catch_panic::test::check_callback(|env| Java_com_example_Example_panic(env), true);
|
||||
//! ```
|
||||
//!
|
||||
//! Since `#[catch_panic]` internally relies on Rust's [catch_unwind] function,
|
||||
//! you **must** build your library with [`panic = "unwind"`] for panics to be caught.
|
||||
//!
|
||||
//! [catch_unwind]: std::panic::catch_unwind
|
||||
//! [`panic = "unwind"`]: https://doc.rust-lang.org/cargo/reference/profiles.html#panic
|
||||
//!
|
||||
//! ### Dummy return values
|
||||
//!
|
||||
//! Even if an exception is thrown, the native callback must return some value to the JVM.
|
||||
//! By default, `#[catch_panic]` will attempt to use the [Default][std::default::Default]
|
||||
//! value of a type as the dummy value to return. This however does not work for `jobject`s
|
||||
//! and related types, so an explicit default must be specified:
|
||||
//!
|
||||
//! ```
|
||||
//! # use jni::JNIEnv;
|
||||
//! # use jni::sys::jobject;
|
||||
//! # use catch_panic::catch_panic;
|
||||
//! #
|
||||
//! #[no_mangle]
|
||||
//! #[catch_panic(default = "std::ptr::null_mut()")]
|
||||
//! pub extern "C" fn Java_com_example_Example_gimmeAnObject(mut env: JNIEnv) -> jobject {
|
||||
//! env.alloc_object("java/lang/Object").unwrap().into_inner()
|
||||
//! }
|
||||
//!
|
||||
//! # catch_panic::test::check_callback(|env| Java_com_example_Example_gimmeAnObject(env), false);
|
||||
//! ```
|
||||
//!
|
||||
//! Any valid expression can be used for the default value.
|
||||
//!
|
||||
//! ### Custom panic payload handlers
|
||||
//!
|
||||
//! For throwing custom exception types or processing the panic payload,
|
||||
//! you can use your own panic payload handler:
|
||||
//!
|
||||
//! ```
|
||||
//! # use std::any::Any;
|
||||
//! # use jni::JNIEnv;
|
||||
//! # use catch_panic::catch_panic;
|
||||
//! #
|
||||
//! pub fn enterprise_certified_handler(mut env: JNIEnv, err: Box<dyn Any + Send + 'static>) {
|
||||
//! let msg = match err.downcast_ref::<&'static str>() {
|
||||
//! Some(s) => *s,
|
||||
//! None => match err.downcast_ref::<String>() {
|
||||
//! Some(s) => &s[..],
|
||||
//! None => "this is a certified `std::panic::panic_any` moment",
|
||||
//! },
|
||||
//! };
|
||||
//! env.throw_new("com/example/ExampleEnterpriseException", msg).unwrap();
|
||||
//! }
|
||||
//!
|
||||
//! #[no_mangle]
|
||||
//! #[catch_panic(handler = "enterprise_certified_handler")]
|
||||
//! pub extern "C" fn Java_com_example_Example_makeFactoryNoises(env: JNIEnv) {
|
||||
//! panic!("<insert factory noises>");
|
||||
//! }
|
||||
//!
|
||||
//! # catch_panic::test::check_callback_with_setup(
|
||||
//! # |env| {
|
||||
//! # let src = std::fs::read("testlib/ExampleEnterpriseException.class")
|
||||
//! # .expect("ExampleEnterpriseException test class file missing!");
|
||||
//! #
|
||||
//! # let object_class = env.find_class("java/lang/Object").unwrap();
|
||||
//! # let object_class_loader = env
|
||||
//! # .call_method(object_class, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])
|
||||
//! # .unwrap();
|
||||
//! #
|
||||
//! # env.define_class(
|
||||
//! # "com/example/ExampleEnterpriseException",
|
||||
//! # object_class_loader.try_into().unwrap(),
|
||||
//! # &src,
|
||||
//! # )
|
||||
//! # .unwrap();
|
||||
//! # },
|
||||
//! # |env| Java_com_example_Example_makeFactoryNoises(env),
|
||||
//! # true,
|
||||
//! # );
|
||||
//! ```
|
||||
|
||||
pub mod handler;
|
||||
pub use catch_panic_macros::catch_panic;
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "catch_panic_macros"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Arc-blroth <45273859+Arc-blroth@users.noreply.github.com>",
|
||||
"Cayden Pocrass <caydenrp9@gmail.com>",
|
||||
"AdityaJain1030 <50159919+AdityaJain1030@users.noreply.github.com>"
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/HermitSocialClub/catch_panic"
|
||||
description = "The proc macro impl for catch_panic"
|
||||
keywords = ["ffi", "jni", "java"]
|
||||
categories = ["api-bindings"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
@@ -1,108 +0,0 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_macro_input, Expr, FnArg, ItemFn, LitStr, Pat, Path};
|
||||
|
||||
const ONLY_FUNCTIONS_MSG: &str = "#[catch_panic] can only be applied to functions";
|
||||
const FIRST_ARG_MSG: &str =
|
||||
"#[catch_panic] requires that this function has at least one argument of type jni::JNIEnv";
|
||||
const INVALID_NAME_MSG: &str = "Invalid function parameter name";
|
||||
|
||||
/// Aborts parsing with the given error.
|
||||
/// This will also emit the original item as-is to suppress red herring unused warnings.
|
||||
macro_rules! abort {
|
||||
($err:expr, $item:expr $(,)?) => {{
|
||||
let err = $err;
|
||||
let item = $item;
|
||||
return TokenStream::from(quote! {
|
||||
#err
|
||||
#item
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
/// Catches a panic and rethrows it as a Java exception.
|
||||
/// See the [crate-level documentation](../catch_panic/index.html) for more information.
|
||||
#[proc_macro_attribute]
|
||||
pub fn catch_panic(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let item = proc_macro2::TokenStream::from(item);
|
||||
|
||||
// parse macro arguments
|
||||
let mut default_value = None;
|
||||
let mut handler = None;
|
||||
let parser = syn::meta::parser(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
default_value = Some(meta.value()?.parse::<LitStr>()?.parse::<Expr>()?);
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("handler") {
|
||||
handler = Some(meta.value()?.parse::<LitStr>()?.parse::<Path>()?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("unsupported argument"))
|
||||
}
|
||||
});
|
||||
|
||||
parse_macro_input!(attr with parser);
|
||||
|
||||
let default_value = match default_value {
|
||||
Some(val) => val.to_token_stream(),
|
||||
None => quote! { ::std::default::Default::default() },
|
||||
};
|
||||
let handler = match handler {
|
||||
Some(val) => val.to_token_stream(),
|
||||
None => quote! { ::catch_panic::handler::default_handler },
|
||||
};
|
||||
|
||||
// parse and validate function
|
||||
let item_span = item.span();
|
||||
let (attrs, vis, sig, block) = match syn::parse2::<ItemFn>(item.clone()) {
|
||||
Ok(ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
block,
|
||||
}) => (attrs, vis, sig, block),
|
||||
Err(_) => abort!(
|
||||
quote_spanned! {item_span=>
|
||||
compile_error!(#ONLY_FUNCTIONS_MSG);
|
||||
},
|
||||
item,
|
||||
),
|
||||
};
|
||||
if sig.inputs.is_empty() {
|
||||
abort!(
|
||||
quote_spanned! {sig.span()=>
|
||||
compile_error!(#FIRST_ARG_MSG);
|
||||
},
|
||||
item,
|
||||
);
|
||||
}
|
||||
let first_arg_name = match sig.inputs.first().unwrap() {
|
||||
FnArg::Receiver(receiver) => abort!(
|
||||
quote_spanned! {receiver.span()=>
|
||||
compile_error!(#FIRST_ARG_MSG);
|
||||
},
|
||||
item,
|
||||
),
|
||||
FnArg::Typed(pat_type) => match pat_type.pat.deref() {
|
||||
Pat::Ident(pat) => pat.ident.clone(),
|
||||
_ => abort!(
|
||||
quote_spanned! {pat_type.span()=>
|
||||
compile_error!(#INVALID_NAME_MSG);
|
||||
},
|
||||
item,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
::catch_panic::handler::__catch_panic(#first_arg_name, #default_value, #handler, move || {
|
||||
#block
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "jnix"
|
||||
description = "High-level extensions to help with the usage of JNI in Rust code"
|
||||
version = "0.5.0"
|
||||
authors = ["Mullvad VPN"]
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
keywords = ["ffi", "java", "jni"]
|
||||
categories = ["external-ffi-bindings"]
|
||||
repository = "https://github.com/mullvad/jnix"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
derive = ["jnix-macros"]
|
||||
|
||||
[dependencies]
|
||||
jni = "0.20"
|
||||
jnix-macros = { version = "0.4", optional = true }
|
||||
once_cell = "1.19"
|
||||
parking_lot = "0.12"
|
||||
|
||||
[dev-dependencies]
|
||||
jnix-macros = { version = "0.4" }
|
||||
@@ -1,39 +0,0 @@
|
||||
use jni::objects::{AutoLocal, JValue};
|
||||
|
||||
/// Returns a value as its [`JValue`] representation.
|
||||
///
|
||||
/// [`JValue`]: JValue
|
||||
pub trait AsJValue<'env> {
|
||||
/// Returns the [`JValue`] representation of the type.
|
||||
///
|
||||
/// [`JValue`]: JValue
|
||||
fn as_jvalue<'borrow>(&'borrow self) -> JValue<'borrow>
|
||||
where
|
||||
'env: 'borrow;
|
||||
}
|
||||
|
||||
impl<'env_borrow, 'env: 'env_borrow> AsJValue<'env> for AutoLocal<'env, 'env_borrow> {
|
||||
fn as_jvalue<'borrow>(&'borrow self) -> JValue<'borrow>
|
||||
where
|
||||
'env: 'borrow,
|
||||
{
|
||||
JValue::Object(self.as_obj())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_for_primitives {
|
||||
( $( $primitive:ty ),* $(,)* ) => {
|
||||
$(
|
||||
impl<'env> AsJValue<'env> for $primitive {
|
||||
fn as_jvalue<'borrow>(&'borrow self) -> JValue<'borrow>
|
||||
where
|
||||
'env: 'borrow,
|
||||
{
|
||||
JValue::from(*self)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_for_primitives!((), bool, u8, i8, u16, i16, i32, i64, f32, f64);
|
||||
@@ -1 +0,0 @@
|
||||
mod std;
|
||||
@@ -1,210 +0,0 @@
|
||||
use crate::{FromJava, JnixEnv};
|
||||
use jni::signature::ReturnType;
|
||||
use jni::{
|
||||
objects::{AutoLocal, JObject, JString, JValue},
|
||||
signature::Primitive,
|
||||
sys::{jboolean, jint, JNI_FALSE},
|
||||
};
|
||||
|
||||
impl<'env, 'sub_env, T> FromJava<'env, JValue<'sub_env>> for T
|
||||
where
|
||||
'env: 'sub_env,
|
||||
T: FromJava<'env, JObject<'sub_env>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = T::JNI_SIGNATURE;
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JValue<'sub_env>) -> Self {
|
||||
match source {
|
||||
JValue::Object(object) => T::from_java(env, object),
|
||||
_ => panic!(
|
||||
"Can't convert non-object Java type. Expected type signature {}",
|
||||
Self::JNI_SIGNATURE
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env, 'borrow, T> FromJava<'env, AutoLocal<'sub_env, 'borrow>> for T
|
||||
where
|
||||
'env: 'sub_env,
|
||||
'sub_env: 'borrow,
|
||||
T: for<'inner_borrow> FromJava<'env, JObject<'inner_borrow>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = T::JNI_SIGNATURE;
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: AutoLocal<'sub_env, 'borrow>) -> Self {
|
||||
T::from_java(env, source.as_obj())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> FromJava<'env, jboolean> for bool {
|
||||
const JNI_SIGNATURE: &'static str = "Z";
|
||||
|
||||
fn from_java(_: &JnixEnv<'env>, source: jboolean) -> Self {
|
||||
source != JNI_FALSE
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env> FromJava<'env, JValue<'sub_env>> for bool
|
||||
where
|
||||
'env: 'sub_env,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Z";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JValue<'sub_env>) -> Self {
|
||||
match source {
|
||||
JValue::Bool(boolean) => bool::from_java(env, boolean),
|
||||
_ => panic!("Can't convert Java type, expected a boolean primitive"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> FromJava<'env, jint> for i32 {
|
||||
const JNI_SIGNATURE: &'static str = "I";
|
||||
|
||||
fn from_java(_: &JnixEnv<'env>, source: jint) -> Self {
|
||||
source as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env> FromJava<'env, JValue<'sub_env>> for i32
|
||||
where
|
||||
'env: 'sub_env,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "I";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JValue<'sub_env>) -> Self {
|
||||
match source {
|
||||
JValue::Int(integer) => i32::from_java(env, integer),
|
||||
_ => panic!("Can't convert Java type, expected an integer primitive"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env> FromJava<'env, JString<'sub_env>> for String
|
||||
where
|
||||
'env: 'sub_env,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/String;";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JString<'sub_env>) -> Self {
|
||||
String::from(
|
||||
env.get_string(source)
|
||||
.expect("Failed to convert from Java String"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env> FromJava<'env, JObject<'sub_env>> for String
|
||||
where
|
||||
'env: 'sub_env,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/String;";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JObject<'sub_env>) -> Self {
|
||||
String::from_java(env, JString::from(source))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env, T> FromJava<'env, JObject<'sub_env>> for Option<T>
|
||||
where
|
||||
'env: 'sub_env,
|
||||
T: FromJava<'env, JObject<'sub_env>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = T::JNI_SIGNATURE;
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JObject<'sub_env>) -> Self {
|
||||
if source.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(T::from_java(env, source))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env, T> FromJava<'env, JString<'sub_env>> for Option<T>
|
||||
where
|
||||
'env: 'sub_env,
|
||||
T: FromJava<'env, JString<'sub_env>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = T::JNI_SIGNATURE;
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JString<'sub_env>) -> Self {
|
||||
if source.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(T::from_java(env, source))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env> FromJava<'env, JObject<'sub_env>> for Option<i32> {
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/Integer;";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JObject<'sub_env>) -> Self {
|
||||
if source.is_null() {
|
||||
None
|
||||
} else {
|
||||
let class = env.get_class("java/lang/Integer");
|
||||
let method_id = env
|
||||
.get_method_id(&class, "intValue", "()I")
|
||||
.expect("Failed to get method ID for Integer.intValue()");
|
||||
let return_type = ReturnType::Primitive(Primitive::Int);
|
||||
|
||||
let int_value = env
|
||||
.call_method_unchecked(source, method_id, return_type, &[])
|
||||
.expect("Failed to call Integer.intValue()")
|
||||
.i()
|
||||
.expect("Call to Integer.intValue() did not return an int primitive");
|
||||
|
||||
Some(int_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, 'sub_env, T> FromJava<'env, JObject<'sub_env>> for Vec<T>
|
||||
where
|
||||
'env: 'sub_env,
|
||||
T: FromJava<'env, JObject<'sub_env>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/util/ArrayList;";
|
||||
|
||||
fn from_java(env: &JnixEnv<'env>, source: JObject<'sub_env>) -> Self {
|
||||
let class = env.get_class("java/util/ArrayList");
|
||||
let size_method_id = env
|
||||
.get_method_id(&class, "size", "()I")
|
||||
.expect("Failed to get method ID for ArrayList.size()");
|
||||
let size_return_type = ReturnType::Primitive(Primitive::Int);
|
||||
|
||||
let item_count = env
|
||||
.call_method_unchecked(source, size_method_id, size_return_type, &[])
|
||||
.expect("Failed to call ArrayList.size()")
|
||||
.i()
|
||||
.expect("Call to ArrayList.size() did not return an int primitive");
|
||||
|
||||
let mut target = Vec::with_capacity(item_count as usize);
|
||||
|
||||
let get_method_id = env
|
||||
.get_method_id(&class, "get", "(I)Ljava/lang/Object;")
|
||||
.expect("Failed to get method ID for ArrayList.get()");
|
||||
let get_return_type = ReturnType::Object;
|
||||
|
||||
for index in 0..item_count {
|
||||
let object = env
|
||||
.call_method_unchecked(
|
||||
source,
|
||||
get_method_id,
|
||||
get_return_type.clone(),
|
||||
&[JValue::from(index).to_jni()],
|
||||
)
|
||||
.expect("Failed to call ArrayList.get()")
|
||||
.l()
|
||||
.expect("Call to ArrayList.get() did not return an object");
|
||||
let item = T::from_java(env, object);
|
||||
|
||||
target.push(item);
|
||||
}
|
||||
|
||||
target
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
mod implementations;
|
||||
|
||||
use crate::JnixEnv;
|
||||
|
||||
/// Conversion from a Java type into its Rust equivalent.
|
||||
///
|
||||
/// A type that implements this trait has an equivalent Java representation.
|
||||
pub trait FromJava<'env, JavaType> {
|
||||
/// The [JNI signature] of the Java type.
|
||||
///
|
||||
/// [JNI signature]: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432
|
||||
const JNI_SIGNATURE: &'static str;
|
||||
|
||||
/// Performs the conversion.
|
||||
fn from_java(env: &JnixEnv<'env>, source: JavaType) -> Self;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
mod std;
|
||||
@@ -1,224 +0,0 @@
|
||||
use crate::{IntoJava, JnixEnv};
|
||||
use jni::sys::jfloat;
|
||||
use jni::{
|
||||
objects::{AutoLocal, JList, JObject, JValue},
|
||||
sys::{jboolean, jdouble, jint, jlong, jshort, jsize, JNI_FALSE, JNI_TRUE},
|
||||
};
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for bool {
|
||||
const JNI_SIGNATURE: &'static str = "Z";
|
||||
|
||||
type JavaType = jboolean;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
if self {
|
||||
JNI_TRUE
|
||||
} else {
|
||||
JNI_FALSE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for i16 {
|
||||
const JNI_SIGNATURE: &'static str = "S";
|
||||
|
||||
type JavaType = jshort;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
self as jshort
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for i32 {
|
||||
const JNI_SIGNATURE: &'static str = "I";
|
||||
|
||||
type JavaType = jint;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
self as jint
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for i64 {
|
||||
const JNI_SIGNATURE: &'static str = "J";
|
||||
|
||||
type JavaType = jlong;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
self as jlong
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for f32 {
|
||||
const JNI_SIGNATURE: &'static str = "F";
|
||||
|
||||
type JavaType = jfloat;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
self as jfloat
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for f64 {
|
||||
const JNI_SIGNATURE: &'static str = "D";
|
||||
|
||||
type JavaType = jdouble;
|
||||
|
||||
fn into_java(self, _: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
self as jdouble
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for &'_ [u8] {
|
||||
const JNI_SIGNATURE: &'static str = "[B";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
let size = self.len();
|
||||
let array = env
|
||||
.new_byte_array(size as jsize)
|
||||
.expect("Failed to create a Java array of bytes");
|
||||
|
||||
let data = unsafe { std::slice::from_raw_parts(self.as_ptr() as *const i8, size) };
|
||||
|
||||
env.set_byte_array_region(array, 0, data)
|
||||
.expect("Failed to copy bytes to Java array");
|
||||
|
||||
env.auto_local(unsafe { JObject::from_raw(array) })
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_java_for_array {
|
||||
($element_type:ty) => {
|
||||
impl_into_java_for_array!(
|
||||
$element_type,
|
||||
0 1 2 3 4 5 6 7
|
||||
8 9 10 11 12 13 14 15
|
||||
16 17 18 19 20 21 22 23
|
||||
24 25 26 27 28 29 30 31
|
||||
32
|
||||
);
|
||||
};
|
||||
|
||||
($element_type:ty, $( $count:tt )*) => {
|
||||
$(
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for [$element_type; $count] {
|
||||
const JNI_SIGNATURE: &'static str = "[B";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
(&self as &[$element_type]).into_java(env)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_java_for_array!(u8);
|
||||
|
||||
impl<'borrow, 'env, T> IntoJava<'borrow, 'env> for Option<T>
|
||||
where
|
||||
'env: 'borrow,
|
||||
T: IntoJava<'borrow, 'env, JavaType = AutoLocal<'env, 'borrow>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = T::JNI_SIGNATURE;
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
match self {
|
||||
Some(t) => t.into_java(env),
|
||||
None => env.auto_local(JObject::null()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env> IntoJava<'borrow, 'env> for Option<bool>
|
||||
where
|
||||
'env: 'borrow,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/Boolean;";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
match self {
|
||||
Some(value) => {
|
||||
let class = env.get_class("java/lang/Boolean");
|
||||
let boxed_boolean = env
|
||||
.new_object(&class, "(Z)V", &[JValue::Bool(value as jboolean)])
|
||||
.expect("Failed to create boxed Boolean object");
|
||||
|
||||
env.auto_local(boxed_boolean)
|
||||
}
|
||||
None => env.auto_local(JObject::null()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env> IntoJava<'borrow, 'env> for Option<i32>
|
||||
where
|
||||
'env: 'borrow,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/Integer;";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
match self {
|
||||
Some(value) => {
|
||||
let class = env.get_class("java/lang/Integer");
|
||||
let boxed_boolean = env
|
||||
.new_object(&class, "(I)V", &[JValue::Int(value as jint)])
|
||||
.expect("Failed to create boxed Integer object");
|
||||
|
||||
env.auto_local(boxed_boolean)
|
||||
}
|
||||
None => env.auto_local(JObject::null()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env, T> IntoJava<'borrow, 'env> for Vec<T>
|
||||
where
|
||||
'env: 'borrow,
|
||||
T: IntoJava<'borrow, 'env, JavaType = AutoLocal<'env, 'borrow>>,
|
||||
{
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/util/ArrayList;";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
let initial_capacity = self.len();
|
||||
let parameters = [JValue::Int(initial_capacity as jint)];
|
||||
|
||||
let class = env.get_class("java/util/ArrayList");
|
||||
let list_object = env
|
||||
.new_object(&class, "(I)V", ¶meters)
|
||||
.expect("Failed to create ArrayList object");
|
||||
|
||||
let list =
|
||||
JList::from_env(env, list_object).expect("Failed to create JList from ArrayList");
|
||||
|
||||
for element in self {
|
||||
list.add(element.into_java(env).as_obj())
|
||||
.expect("Failed to add element to ArrayList");
|
||||
}
|
||||
|
||||
env.auto_local(list_object)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'borrow, 'env: 'borrow> IntoJava<'borrow, 'env> for String {
|
||||
const JNI_SIGNATURE: &'static str = "Ljava/lang/String;";
|
||||
|
||||
type JavaType = AutoLocal<'env, 'borrow>;
|
||||
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType {
|
||||
let jstring = env.new_string(&self).expect("Failed to create Java String");
|
||||
|
||||
env.auto_local(jstring)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
mod implementations;
|
||||
|
||||
use crate::{AsJValue, JnixEnv};
|
||||
|
||||
/// Conversion from a type into its Java representation.
|
||||
///
|
||||
/// A type that implements this trait has an equivalent Java representation.
|
||||
pub trait IntoJava<'borrow, 'env: 'borrow> {
|
||||
/// The [JNI signature] of the Java type.
|
||||
///
|
||||
/// [JNI signature]: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432
|
||||
const JNI_SIGNATURE: &'static str;
|
||||
|
||||
/// The Rust type that wraps a reference to the Java type.
|
||||
type JavaType: AsJValue<'env>;
|
||||
|
||||
/// Performs the conversion.
|
||||
fn into_java(self, env: &'borrow JnixEnv<'env>) -> Self::JavaType;
|
||||
|
||||
/// Returns the JNI signature of the Java type.
|
||||
///
|
||||
/// This is a helper method to obtain the `JNI_SIGNATURE` from a value without knowing its
|
||||
/// exact type name.
|
||||
fn jni_signature(&self) -> &'static str {
|
||||
Self::JNI_SIGNATURE
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use jni::{
|
||||
objects::{GlobalRef, JObject},
|
||||
JNIEnv,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
||||
|
||||
static CLASS_CACHE: Lazy<Mutex<HashMap<String, GlobalRef>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
/// Provides access to JNI functions.
|
||||
///
|
||||
/// Also provides an internal cache for loaded classes.
|
||||
pub struct JnixEnv<'env> {
|
||||
pub env: JNIEnv<'env>,
|
||||
}
|
||||
|
||||
impl<'env> From<JNIEnv<'env>> for JnixEnv<'env> {
|
||||
fn from(env: JNIEnv<'env>) -> Self {
|
||||
JnixEnv { env }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> Deref for JnixEnv<'env> {
|
||||
type Target = JNIEnv<'env>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.env
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> JnixEnv<'env> {
|
||||
/// Retrieves a global reference to a Class object.
|
||||
///
|
||||
/// Tries to retrieve it from the class cache. If it's not present in the cache, it is loaded
|
||||
/// into the cache first.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if the class can't be loaded or if a global reference can't be
|
||||
/// obtained to the Class object.
|
||||
pub fn get_class<'a>(&self, class_name: impl Into<Cow<'a, str>>) -> GlobalRef {
|
||||
let class_name = class_name.into();
|
||||
let mut cache = CLASS_CACHE.lock();
|
||||
|
||||
if let Some(class) = cache.get(class_name.as_ref()) {
|
||||
class.clone()
|
||||
} else {
|
||||
let class = self.load_class(class_name.as_ref());
|
||||
|
||||
cache.insert(class_name.into_owned(), class.clone());
|
||||
|
||||
class
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a class and stores it in the class cache.
|
||||
pub fn preload_classes(&self, class_names: impl IntoIterator<Item = impl Into<String>>) {
|
||||
let mut cache = CLASS_CACHE.lock();
|
||||
|
||||
for class_name in class_names {
|
||||
let class_name = class_name.into();
|
||||
let class = self.load_class(&class_name);
|
||||
|
||||
cache.insert(class_name, class);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_class(&self, class_name: impl AsRef<str>) -> GlobalRef {
|
||||
let class_name = class_name.as_ref();
|
||||
let local_ref = self
|
||||
.env
|
||||
.find_class(class_name)
|
||||
.expect(&format!("Failed to find {} Java class", class_name));
|
||||
|
||||
self.env.new_global_ref(JObject::from(local_ref)).expect(
|
||||
"Failed to convert local reference to Java class object into a global reference",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
//! This crate provides high-level extensions to help with the usage of [JNI] in Rust code. Internally,
|
||||
//! it uses the [`jni-rs`] crate for the low-level JNI operations.
|
||||
//!
|
||||
//! Some helper traits are provided, such as:
|
||||
//!
|
||||
//! - [`AsJValue`]: for allowing a JNI type to be convected to a `JValue` wrapper type.
|
||||
//! - [`IntoJava`]: for allowing a Rust type to be converted to a Java type.
|
||||
//! - [`FromJava`]: for allowing a Rust type to be created from a Java type.
|
||||
//!
|
||||
//! A [`JnixEnv`] helper type is also provided, which is a [`JNIEnv`] wrapper that contains an
|
||||
//! internal class cache for preloaded classes.
|
||||
//!
|
||||
//! If compiled with the `derive` feature flag, the crate also exports procedural macros to
|
||||
//! [derive `IntoJava`] and to [derive `FromJava`], which makes writing conversion code a lot
|
||||
//! easier. An example would be:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use jnix::{
|
||||
//! jni::{objects::JObject, JNIEnv},
|
||||
//! JnixEnv, FromJava, IntoJava,
|
||||
//! };
|
||||
//!
|
||||
//! // Rust type definition
|
||||
//! #[derive(Default, FromJava, IntoJava)]
|
||||
//! #[jnix(package = "my.package")]
|
||||
//! pub struct MyData {
|
||||
//! number: i32,
|
||||
//! string: String,
|
||||
//! }
|
||||
//!
|
||||
//! // A JNI function called from Java that creates a `MyData` Rust type, converts it to a Java
|
||||
//! // type and returns it.
|
||||
//! #[no_mangle]
|
||||
//! #[allow(non_snake_case)]
|
||||
//! pub extern "system" fn Java_my_package_JniClass_getData<'env>(
|
||||
//! env: JNIEnv<'env>,
|
||||
//! _this: JObject<'env>,
|
||||
//! data: JObject<'env>,
|
||||
//! ) -> JObject<'env> {
|
||||
//! // Create the `JnixEnv` wrapper
|
||||
//! let env = JnixEnv::from(env);
|
||||
//!
|
||||
//! // Convert parameter to Rust type
|
||||
//! let data = MyData::from_java(&env, data);
|
||||
//!
|
||||
//! // Create a new `MyData` object by converting from the Rust type. Since a smart pointer is
|
||||
//! // returned from `into_java`, the inner object must be "leaked" sothat the garbage collector
|
||||
//! // can own it afterwards
|
||||
//! data.into_java(&env).forget()
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ```java
|
||||
//! package my.package;
|
||||
//!
|
||||
//! public class MyData {
|
||||
//! public MyData(int number, String string) {
|
||||
//! // This is the constructor that is called by the generated `IntoJava` code
|
||||
//! //
|
||||
//! // Note that the fields don't actually have to exist, the only thing that's necessary
|
||||
//! // is for the target Java class to have a constructor with the expected type signature
|
||||
//! // following the field order of the Rust type.
|
||||
//! }
|
||||
//!
|
||||
//! // These getters are called by the generated `FromJava` code
|
||||
//! public int getNumber() {
|
||||
//! return 10;
|
||||
//! }
|
||||
//!
|
||||
//! public String getString() {
|
||||
//! return "string value";
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [JNI]: https://en.wikipedia.org./wiki/Java_Native_Interface
|
||||
//! [`jni-rs`]: https://crates.io/crates/jni
|
||||
//! [`JNIEnv`]: jni::JNIEnv
|
||||
//! [`AsJValue`]: AsJValue
|
||||
//! [`IntoJava`]: IntoJava
|
||||
//! [`FromJava`]: FromJava
|
||||
//! [`JnixEnv`]: JnixEnv
|
||||
//! [derive `IntoJava`]: ../jnix_macros/derive.IntoJava.html
|
||||
//! [derive `FromJava`]: ../jnix_macros/derive.FromJava.html
|
||||
|
||||
pub extern crate jni;
|
||||
|
||||
mod as_jvalue;
|
||||
mod from_java;
|
||||
mod into_java;
|
||||
mod jnix_env;
|
||||
|
||||
pub use self::{as_jvalue::AsJValue, from_java::FromJava, into_java::IntoJava, jnix_env::JnixEnv};
|
||||
#[cfg(feature = "derive")]
|
||||
pub use jnix_macros::{FromJava, IntoJava};
|
||||
@@ -2,22 +2,24 @@ mod parser;
|
||||
|
||||
extern crate android_logger;
|
||||
extern crate apply;
|
||||
extern crate catch_panic;
|
||||
extern crate jni;
|
||||
extern crate jni_fn;
|
||||
extern crate jnix;
|
||||
extern crate jnix_macros;
|
||||
extern crate log;
|
||||
extern crate once_cell;
|
||||
extern crate quick_xml;
|
||||
extern crate regex_lite;
|
||||
extern crate serde;
|
||||
extern crate tl;
|
||||
|
||||
use android_logger::Config;
|
||||
use jnix::jni::objects::JByteBuffer;
|
||||
use jnix::jni::sys::{jint, JavaVM, JNI_VERSION_1_6};
|
||||
use jnix::JnixEnv;
|
||||
use jni::objects::JByteBuffer;
|
||||
use jni::sys::{jint, JavaVM, JNI_VERSION_1_6};
|
||||
use jni::JNIEnv;
|
||||
use log::LevelFilter;
|
||||
use serde::Serialize;
|
||||
use std::ffi::c_void;
|
||||
use std::io::Write;
|
||||
use std::panic::{catch_unwind, UnwindSafe};
|
||||
use std::ptr::slice_from_raw_parts;
|
||||
use std::str::from_utf8_unchecked;
|
||||
use tl::{Bytes, Node, NodeHandle, Parser, VDom};
|
||||
@@ -33,6 +35,28 @@ macro_rules! regex {
|
||||
const EHGT_PREFIX: &str = "https://ehgt.org/";
|
||||
const EX_PREFIX: &str = "https://s.exhentai.org/";
|
||||
|
||||
struct PtrCursor {
|
||||
ptr: *mut u8,
|
||||
pos: i32,
|
||||
}
|
||||
|
||||
impl Write for PtrCursor {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let len = buf.len();
|
||||
unsafe {
|
||||
self.ptr.copy_from(buf.as_ptr(), len);
|
||||
self.ptr = self.ptr.add(len);
|
||||
self.pos += len as i32;
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
// Nothing
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_html(str: &str) {
|
||||
if !str.contains('<') {
|
||||
panic!("{}", str)
|
||||
@@ -68,18 +92,44 @@ where
|
||||
handle.get(parser)
|
||||
}
|
||||
|
||||
fn parse_bytebuffer<F, R>(env: &mut JnixEnv, str: JByteBuffer, limit: jint, mut f: F) -> Option<R>
|
||||
fn parse_marshal_inplace<F, R>(env: &mut JNIEnv, str: JByteBuffer, limit: jint, mut f: F) -> i32
|
||||
where
|
||||
F: FnMut(&VDom, &Parser, &JnixEnv, &str) -> Option<R>,
|
||||
F: FnMut(&VDom, &Parser, &str) -> Option<R> + UnwindSafe,
|
||||
R: Serialize,
|
||||
{
|
||||
let ptr = env.get_direct_buffer_address(str).ok()?;
|
||||
let ptr = env.get_direct_buffer_address(&str).unwrap();
|
||||
let html = unsafe {
|
||||
let buff = slice_from_raw_parts(ptr, limit as usize);
|
||||
from_utf8_unchecked(&*buff)
|
||||
};
|
||||
let dom = tl::parse(html, tl::ParserOptions::default()).ok()?;
|
||||
let parser = dom.parser();
|
||||
f(&dom, parser, env, html)
|
||||
let f = move || {
|
||||
let dom = tl::parse(html, tl::ParserOptions::default()).unwrap();
|
||||
let parser = dom.parser();
|
||||
let result = f(&dom, parser, html);
|
||||
match result {
|
||||
// Nothing to marshal
|
||||
None => 0,
|
||||
Some(value) => {
|
||||
let mut cursor = PtrCursor { ptr, pos: 0 };
|
||||
serde_json::to_writer(&mut cursor, &value).unwrap();
|
||||
cursor.pos
|
||||
}
|
||||
}
|
||||
};
|
||||
match catch_unwind(f) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
let msg = match err.downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match err.downcast_ref::<String>() {
|
||||
Some(s) => &s[..],
|
||||
None => "Box<dyn Any>",
|
||||
},
|
||||
};
|
||||
env.throw_new("java/lang/RuntimeException", msg).unwrap();
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node_handle_attr<'a>(
|
||||
|
||||
@@ -1,57 +1,63 @@
|
||||
use apply::Also;
|
||||
use catch_panic::catch_panic;
|
||||
use jni::objects::{JByteBuffer, JClass};
|
||||
use jni::sys::jint;
|
||||
use jni::JNIEnv;
|
||||
use jni_fn::jni_fn;
|
||||
use jnix::jni::objects::{JByteBuffer, JClass};
|
||||
use jnix::jni::sys::{jint, jintArray, jobjectArray};
|
||||
use jnix::jni::JNIEnv;
|
||||
use jnix::JnixEnv;
|
||||
use parse_bytebuffer;
|
||||
use parse_marshal_inplace;
|
||||
use parser::list::{parse_info_list, GalleryListResult};
|
||||
use quick_xml::escape::unescape;
|
||||
use serde::Serialize;
|
||||
use tl::Parser;
|
||||
use tl::VDom;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct FavResult {
|
||||
catArray: Vec<String>,
|
||||
countArray: Vec<i32>,
|
||||
galleryListResult: GalleryListResult,
|
||||
}
|
||||
|
||||
fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Option<FavResult> {
|
||||
if html.contains("This page requires you to log on.</p>") {
|
||||
panic!("Not logged in!")
|
||||
}
|
||||
let vec: Vec<(String, i32)> = dom
|
||||
.get_elements_by_class_name("fp")
|
||||
.enumerate()
|
||||
.filter_map(|(i, e)| {
|
||||
if i == 10 {
|
||||
return None;
|
||||
}
|
||||
let top = e.get(parser)?.children()?;
|
||||
let children = top.top();
|
||||
let cat = children[5].get(parser)?.inner_text(parser);
|
||||
let name = unescape(&cat).ok()?.trim().to_string();
|
||||
let count = children[1]
|
||||
.get(parser)?
|
||||
.inner_text(parser)
|
||||
.parse::<i32>()
|
||||
.ok()?;
|
||||
Some((name, count))
|
||||
})
|
||||
.collect();
|
||||
if vec.len() == 10 {
|
||||
let list = parse_info_list(dom, parser, html)?;
|
||||
let cat = vec.iter().cloned().unzip();
|
||||
Some(FavResult {
|
||||
catArray: cat.0,
|
||||
countArray: cat.1,
|
||||
galleryListResult: list,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[catch_panic(default = "std::ptr::null_mut()")]
|
||||
#[allow(non_snake_case)]
|
||||
#[jni_fn("com.hippo.ehviewer.client.parser.FavoritesParserKt")]
|
||||
pub fn parseFav(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
input: JByteBuffer,
|
||||
limit: jint,
|
||||
str: jobjectArray,
|
||||
) -> jintArray {
|
||||
let mut env = JnixEnv { env };
|
||||
let vec = parse_bytebuffer(&mut env, input, limit, |dom, parser, env, html| {
|
||||
if html.contains("This page requires you to log on.</p>") {
|
||||
panic!("Not logged in!")
|
||||
}
|
||||
let vec: Vec<i32> = dom
|
||||
.get_elements_by_class_name("fp")
|
||||
.enumerate()
|
||||
.filter_map(|(i, e)| {
|
||||
if i == 10 {
|
||||
return None;
|
||||
}
|
||||
let top = e.get(parser)?.children()?;
|
||||
let children = top.top();
|
||||
let cat = children[5].get(parser)?.inner_text(parser);
|
||||
let name = unescape(&cat).ok()?;
|
||||
env.set_object_array_element(str, i as i32, env.new_string(name.trim()).ok()?)
|
||||
.ok()?;
|
||||
children[1]
|
||||
.get(parser)?
|
||||
.inner_text(parser)
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
})
|
||||
.collect();
|
||||
if vec.len() == 10 {
|
||||
Some(vec)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn parseFav(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
|
||||
parse_marshal_inplace(&mut env, input, limit, |dom, parser, html| {
|
||||
parse_fav(dom, parser, html)
|
||||
})
|
||||
.unwrap();
|
||||
env.new_int_array(10)
|
||||
.unwrap()
|
||||
.also(|it| env.set_int_array_region(*it, 0, &vec).unwrap())
|
||||
}
|
||||
@@ -1,42 +1,40 @@
|
||||
use catch_panic::catch_panic;
|
||||
use get_vdom_first_element_by_class_name;
|
||||
use jni::objects::{JByteBuffer, JClass};
|
||||
use jni::sys::jint;
|
||||
use jni::JNIEnv;
|
||||
use jni_fn::jni_fn;
|
||||
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 {get_vdom_first_element_by_class_name, parse_bytebuffer};
|
||||
use parse_marshal_inplace;
|
||||
use serde::Serialize;
|
||||
use tl::Parser;
|
||||
use tl::VDom;
|
||||
|
||||
#[derive(Default, IntoJava)]
|
||||
#[allow(non_snake_case)]
|
||||
#[jnix(package = "com.hippo.ehviewer.client.parser")]
|
||||
pub struct Limits {
|
||||
#[derive(Serialize)]
|
||||
struct Limits {
|
||||
current: i32,
|
||||
maximum: i32,
|
||||
resetCost: i32,
|
||||
}
|
||||
|
||||
fn parse_limit(dom: &VDom, parser: &Parser) -> Option<Limits> {
|
||||
let iter = get_vdom_first_element_by_class_name(dom, "homebox")?
|
||||
.as_tag()?
|
||||
.query_selector(parser, "strong")?;
|
||||
let vec: Vec<i32> = iter
|
||||
.filter_map(|e| e.get(parser)?.inner_text(parser).parse::<i32>().ok())
|
||||
.collect();
|
||||
Some(Limits {
|
||||
current: vec[0],
|
||||
maximum: vec[1],
|
||||
resetCost: vec[2],
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[catch_panic(default = "std::ptr::null_mut()")]
|
||||
#[allow(non_snake_case)]
|
||||
#[jni_fn("com.hippo.ehviewer.client.parser.HomeParserKt")]
|
||||
pub fn parseLimit(env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jobject {
|
||||
let mut env = JnixEnv { env };
|
||||
parse_bytebuffer(&mut env, input, limit, |dom, parser, _, _| {
|
||||
let iter = get_vdom_first_element_by_class_name(dom, "homebox")?
|
||||
.as_tag()?
|
||||
.query_selector(parser, "strong")?;
|
||||
let vec: Vec<i32> = iter
|
||||
.filter_map(|e| e.get(parser)?.inner_text(parser).parse::<i32>().ok())
|
||||
.collect();
|
||||
Some(Limits {
|
||||
current: vec[0],
|
||||
maximum: vec[1],
|
||||
resetCost: vec[2],
|
||||
})
|
||||
pub fn parseLimit(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint {
|
||||
parse_marshal_inplace(&mut env, input, limit, |dom, parser, _| {
|
||||
parse_limit(dom, parser)
|
||||
})
|
||||
.unwrap()
|
||||
.into_java(&env)
|
||||
.forget()
|
||||
.into_raw()
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
use catch_panic::catch_panic;
|
||||
use check_html;
|
||||
use jni::objects::{JByteBuffer, JClass};
|
||||
use jni::sys::jint;
|
||||
use jni::JNIEnv;
|
||||
use jni_fn::jni_fn;
|
||||
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_marshal_inplace;
|
||||
use quick_xml::escape::unescape;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Index;
|
||||
use tl::{Node, Parser};
|
||||
use {check_html, parse_bytebuffer};
|
||||
use tl::{Node, Parser, VDom};
|
||||
use {get_element_by_id, get_vdom_first_element_by_class_name};
|
||||
use {get_first_element_by_class_name, query_childs_first_match_attr};
|
||||
use {get_node_attr, get_node_handle_attr, regex};
|
||||
use {EHGT_PREFIX, EX_PREFIX};
|
||||
|
||||
#[derive(Default, IntoJava)]
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[jnix(package = "com.hippo.ehviewer.client.data")]
|
||||
pub struct BaseGalleryInfo {
|
||||
struct BaseGalleryInfo {
|
||||
gid: i64,
|
||||
token: String,
|
||||
title: String,
|
||||
@@ -40,9 +38,8 @@ pub struct BaseGalleryInfo {
|
||||
favoriteNote: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, IntoJava)]
|
||||
#[derive(Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
#[jnix(package = "com.hippo.ehviewer.client.parser")]
|
||||
pub struct GalleryListResult {
|
||||
prev: Option<String>,
|
||||
next: Option<String>,
|
||||
@@ -193,57 +190,50 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option<BaseGalleryInfo> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_info_list(dom: &VDom, parser: &Parser, str: &str) -> Option<GalleryListResult> {
|
||||
check_html(str);
|
||||
if str.contains("<p>You do not have any watched tags") {
|
||||
panic!("No watched tags!")
|
||||
}
|
||||
if str.contains("No hits found</p>") || str.contains("No unfiltered results found") {
|
||||
panic!("No hits found!")
|
||||
}
|
||||
let itg = get_vdom_first_element_by_class_name(dom, "itg")?;
|
||||
let children = itg.children()?;
|
||||
let iter = children.top().iter();
|
||||
let info: Vec<BaseGalleryInfo> = iter
|
||||
.filter_map(|x| parse_gallery_info(x.get(parser)?, parser))
|
||||
.collect();
|
||||
let prev = dom.get_element_by_id("uprev").and_then(|e| {
|
||||
let str = get_node_handle_attr(&e, parser, "href")?;
|
||||
Some(
|
||||
regex!("prev=(\\d+(-\\d+)?)")
|
||||
.captures(str)?
|
||||
.index(1)
|
||||
.to_string(),
|
||||
)
|
||||
});
|
||||
let next = dom.get_element_by_id("unext").and_then(|e| {
|
||||
let str = get_node_handle_attr(&e, parser, "href")?;
|
||||
Some(
|
||||
regex!("next=(\\d+(-\\d+)?)")
|
||||
.captures(str)?
|
||||
.index(1)
|
||||
.to_string(),
|
||||
)
|
||||
});
|
||||
(!info.is_empty()).then_some(GalleryListResult {
|
||||
prev,
|
||||
next,
|
||||
galleryInfoList: info,
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[catch_panic(default = "std::ptr::null_mut()")]
|
||||
#[allow(non_snake_case)]
|
||||
#[jni_fn("com.hippo.ehviewer.client.parser.GalleryListParserKt")]
|
||||
pub fn parseGalleryInfoList(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JByteBuffer,
|
||||
limit: jint,
|
||||
) -> jobject {
|
||||
let mut env = JnixEnv { env };
|
||||
parse_bytebuffer(&mut env, buffer, limit, |dom, parser, _, str| {
|
||||
check_html(str);
|
||||
if str.contains("<p>You do not have any watched tags") {
|
||||
panic!("No watched tags!")
|
||||
}
|
||||
if str.contains("No hits found</p>") || str.contains("No unfiltered results found") {
|
||||
panic!("No hits found!")
|
||||
}
|
||||
let itg = get_vdom_first_element_by_class_name(dom, "itg")?;
|
||||
let children = itg.children()?;
|
||||
let iter = children.top().iter();
|
||||
let info: Vec<BaseGalleryInfo> = iter
|
||||
.filter_map(|x| parse_gallery_info(x.get(parser)?, parser))
|
||||
.collect();
|
||||
let prev = dom.get_element_by_id("uprev").and_then(|e| {
|
||||
let str = get_node_handle_attr(&e, parser, "href")?;
|
||||
Some(
|
||||
regex!("prev=(\\d+(-\\d+)?)")
|
||||
.captures(str)?
|
||||
.index(1)
|
||||
.to_string(),
|
||||
)
|
||||
});
|
||||
let next = dom.get_element_by_id("unext").and_then(|e| {
|
||||
let str = get_node_handle_attr(&e, parser, "href")?;
|
||||
Some(
|
||||
regex!("next=(\\d+(-\\d+)?)")
|
||||
.captures(str)?
|
||||
.index(1)
|
||||
.to_string(),
|
||||
)
|
||||
});
|
||||
(!info.is_empty()).then_some(GalleryListResult {
|
||||
prev,
|
||||
next,
|
||||
galleryInfoList: info,
|
||||
})
|
||||
pub fn parseGalleryInfoList(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint {
|
||||
parse_marshal_inplace(&mut env, buffer, limit, |dom, parser, str| {
|
||||
parse_info_list(dom, parser, str)
|
||||
})
|
||||
.unwrap()
|
||||
.into_java(&env)
|
||||
.forget()
|
||||
.into_raw()
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
use catch_panic::catch_panic;
|
||||
use jni::objects::{JByteBuffer, JClass};
|
||||
use jni::sys::jint;
|
||||
use jni::JNIEnv;
|
||||
use jni_fn::jni_fn;
|
||||
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 regex;
|
||||
use serde::Serialize;
|
||||
use tl::{Parser, VDom};
|
||||
use {parse_marshal_inplace, regex};
|
||||
|
||||
#[derive(Default, IntoJava)]
|
||||
#[jnix(package = "com.hippo.ehviewer.client.parser")]
|
||||
pub struct Torrent {
|
||||
#[derive(Serialize)]
|
||||
struct Torrent {
|
||||
posted: String,
|
||||
size: String,
|
||||
seeds: i32,
|
||||
@@ -21,30 +18,33 @@ pub struct Torrent {
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn parse_torrent_list(dom: &VDom, parser: &Parser) -> Option<Vec<Torrent>> {
|
||||
let list = dom.query_selector("table")?.filter_map(|e| {
|
||||
let html = e.get(parser)?.inner_html(parser);
|
||||
if html.contains("Expunged") {
|
||||
None
|
||||
} else {
|
||||
let grp = regex!("</span> ([0-9-]+) [0-9:]+</td>[\\s\\S]+</span> ([0-9.]+ [KMGT]iB)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span>([^<]+)</td>[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a>").captures(&html).unwrap();
|
||||
let name = unescape(&grp[8]).ok()?;
|
||||
Some(Torrent {
|
||||
posted: grp[1].to_string(),
|
||||
size: grp[2].to_string(),
|
||||
seeds: grp[3].parse().ok()?,
|
||||
peers: grp[4].parse().ok()?,
|
||||
downloads: grp[5].parse().ok()?,
|
||||
url: grp[7].to_string(),
|
||||
name: name.to_string()
|
||||
})
|
||||
}
|
||||
}).collect::<Vec<Torrent>>();
|
||||
Some(list)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[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, buffer: JByteBuffer, limit: jint) -> jobject {
|
||||
let mut env = JnixEnv { env };
|
||||
parse_bytebuffer(&mut env, buffer, limit, |dom, parser, _, _| {
|
||||
Some(dom.query_selector("table")?.filter_map(|e| {
|
||||
let html = e.get(parser)?.inner_html(parser);
|
||||
if html.contains("Expunged") {
|
||||
None
|
||||
} else {
|
||||
let grp = regex!("</span> ([0-9-]+) [0-9:]+</td>[\\s\\S]+</span> ([0-9.]+ [KMGT]iB)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span> ([0-9]+)</td>[\\s\\S]+</span>([^<]+)</td>[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)</a>").captures(&html).unwrap();
|
||||
let name = unescape(&grp[8]).ok()?;
|
||||
Some(Torrent {
|
||||
posted: grp[1].to_string(),
|
||||
size: grp[2].to_string(),
|
||||
seeds: grp[3].parse().ok()?,
|
||||
peers: grp[4].parse().ok()?,
|
||||
downloads: grp[5].parse().ok()?,
|
||||
url: grp[7].to_string(),
|
||||
name: name.to_string()
|
||||
})
|
||||
}
|
||||
}).collect::<Vec<Torrent>>())
|
||||
}).unwrap().into_java(&env).forget().into_raw()
|
||||
pub fn parseTorrent(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint {
|
||||
parse_marshal_inplace(&mut env, buffer, limit, |dom, parser, _html| {
|
||||
parse_torrent_list(dom, parser)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user