diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt
index 42d5e630b..b28cd2b7f 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt
@@ -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()
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt
index 2b8f21315..63ba2c4e8 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt
@@ -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,
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ByteBufferSource.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ByteBufferSource.kt
new file mode 100644
index 000000000..ac81befa3
--- /dev/null
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ByteBufferSource.kt
@@ -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 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() }
+}
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/FavoritesParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/FavoritesParser.kt
index a652fc3f5..3da9a1b11 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/FavoritesParser.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/FavoritesParser.kt
@@ -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,
+ val countArray: List,
+ 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(10)
- val countArray = runSuspendCatching {
- parseFav(body, favCat = catArray).also { check(it.isNotEmpty()) }
+ fun parse(body: ByteBuffer): FavParserResult {
+ return runSuspendCatching {
+ unmarshalParsingAs(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,
- 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): IntArray
+private external fun parseFav(body: ByteBuffer, limit: Int = body.limit()): Int
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryListParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryListParser.kt
index 6ff0167a9..1ad94811c 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryListParser.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryListParser.kt
@@ -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(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,
)
-private external fun parseGalleryInfoList(body: ByteBuffer, size: Int = body.limit()): GalleryListResult
+private external fun parseGalleryInfoList(body: ByteBuffer, size: Int = body.limit()): Int
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt
index fa70620d9..e70220cc6 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt
@@ -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(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
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/TorrentParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/TorrentParser.kt
index d91314185..9e47ee8d3 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/TorrentParser.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/TorrentParser.kt
@@ -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(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
-private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): ArrayList
+private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): Int
diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt
index a37fbef48..c36e3f8cd 100644
--- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt
+++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt
@@ -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)
diff --git a/app/src/main/rust/Cargo.toml b/app/src/main/rust/Cargo.toml
index 20048631b..9c3e05898 100644
--- a/app/src/main/rust/Cargo.toml
+++ b/app/src/main/rust/Cargo.toml
@@ -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
diff --git a/app/src/main/rust/catch_panic/Cargo.toml b/app/src/main/rust/catch_panic/Cargo.toml
deleted file mode 100644
index f0075b086..000000000
--- a/app/src/main/rust/catch_panic/Cargo.toml
+++ /dev/null
@@ -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 ",
- "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" }
diff --git a/app/src/main/rust/catch_panic/src/handler.rs b/app/src/main/rust/catch_panic/src/handler.rs
deleted file mode 100644
index 2fbf603e6..000000000
--- a/app/src/main/rust/catch_panic/src/handler.rs
+++ /dev/null
@@ -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(env: JNIEnv, default: R, handler: H, f: F) -> R
-where
- F: FnOnce() -> R + UnwindSafe,
- H: FnOnce(JNIEnv, Box),
-{
- 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) {
- let msg = match err.downcast_ref::<&'static str>() {
- Some(s) => *s,
- None => match err.downcast_ref::() {
- Some(s) => &s[..],
- None => "Box",
- },
- };
- env.throw_new("java/lang/RuntimeException", msg).unwrap();
-}
diff --git a/app/src/main/rust/catch_panic/src/lib.rs b/app/src/main/rust/catch_panic/src/lib.rs
deleted file mode 100644
index a5936a76d..000000000
--- a/app/src/main/rust/catch_panic/src/lib.rs
+++ /dev/null
@@ -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) {
-//! let msg = match err.downcast_ref::<&'static str>() {
-//! Some(s) => *s,
-//! None => match err.downcast_ref::() {
-//! 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!("");
-//! }
-//!
-//! # 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;
diff --git a/app/src/main/rust/catch_panic_macros/Cargo.toml b/app/src/main/rust/catch_panic_macros/Cargo.toml
deleted file mode 100644
index 137878c44..000000000
--- a/app/src/main/rust/catch_panic_macros/Cargo.toml
+++ /dev/null
@@ -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 ",
- "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"] }
diff --git a/app/src/main/rust/catch_panic_macros/src/lib.rs b/app/src/main/rust/catch_panic_macros/src/lib.rs
deleted file mode 100644
index 174a3d81a..000000000
--- a/app/src/main/rust/catch_panic_macros/src/lib.rs
+++ /dev/null
@@ -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::()?.parse::()?);
- Ok(())
- } else if meta.path.is_ident("handler") {
- handler = Some(meta.value()?.parse::()?.parse::()?);
- 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::(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
- })
- }
- })
-}
diff --git a/app/src/main/rust/jnix/Cargo.toml b/app/src/main/rust/jnix/Cargo.toml
deleted file mode 100644
index fad21cfec..000000000
--- a/app/src/main/rust/jnix/Cargo.toml
+++ /dev/null
@@ -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" }
diff --git a/app/src/main/rust/jnix/src/as_jvalue.rs b/app/src/main/rust/jnix/src/as_jvalue.rs
deleted file mode 100644
index a65fcb119..000000000
--- a/app/src/main/rust/jnix/src/as_jvalue.rs
+++ /dev/null
@@ -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);
diff --git a/app/src/main/rust/jnix/src/from_java/implementations/mod.rs b/app/src/main/rust/jnix/src/from_java/implementations/mod.rs
deleted file mode 100644
index 517d00a18..000000000
--- a/app/src/main/rust/jnix/src/from_java/implementations/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-mod std;
diff --git a/app/src/main/rust/jnix/src/from_java/implementations/std/mod.rs b/app/src/main/rust/jnix/src/from_java/implementations/std/mod.rs
deleted file mode 100644
index 874951fc2..000000000
--- a/app/src/main/rust/jnix/src/from_java/implementations/std/mod.rs
+++ /dev/null
@@ -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
-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
-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 {
- 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
-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
- }
-}
diff --git a/app/src/main/rust/jnix/src/from_java/mod.rs b/app/src/main/rust/jnix/src/from_java/mod.rs
deleted file mode 100644
index c8758451e..000000000
--- a/app/src/main/rust/jnix/src/from_java/mod.rs
+++ /dev/null
@@ -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;
-}
diff --git a/app/src/main/rust/jnix/src/into_java/implementations/mod.rs b/app/src/main/rust/jnix/src/into_java/implementations/mod.rs
deleted file mode 100644
index 517d00a18..000000000
--- a/app/src/main/rust/jnix/src/into_java/implementations/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-mod std;
diff --git a/app/src/main/rust/jnix/src/into_java/implementations/std/mod.rs b/app/src/main/rust/jnix/src/into_java/implementations/std/mod.rs
deleted file mode 100644
index f59c70215..000000000
--- a/app/src/main/rust/jnix/src/into_java/implementations/std/mod.rs
+++ /dev/null
@@ -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
-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
-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
-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
-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)
- }
-}
diff --git a/app/src/main/rust/jnix/src/into_java/mod.rs b/app/src/main/rust/jnix/src/into_java/mod.rs
deleted file mode 100644
index ea622b4f0..000000000
--- a/app/src/main/rust/jnix/src/into_java/mod.rs
+++ /dev/null
@@ -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
- }
-}
diff --git a/app/src/main/rust/jnix/src/jnix_env.rs b/app/src/main/rust/jnix/src/jnix_env.rs
deleted file mode 100644
index 849197969..000000000
--- a/app/src/main/rust/jnix/src/jnix_env.rs
+++ /dev/null
@@ -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>> =
- 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> 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>) -> 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- >) {
- 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) -> 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",
- )
- }
-}
diff --git a/app/src/main/rust/jnix/src/lib.rs b/app/src/main/rust/jnix/src/lib.rs
deleted file mode 100644
index c003fa14f..000000000
--- a/app/src/main/rust/jnix/src/lib.rs
+++ /dev/null
@@ -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};
diff --git a/app/src/main/rust/src/lib.rs b/app/src/main/rust/src/lib.rs
index 95065a1be..3214e2dd9 100644
--- a/app/src/main/rust/src/lib.rs
+++ b/app/src/main/rust/src/lib.rs
@@ -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 {
+ 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(env: &mut JnixEnv, str: JByteBuffer, limit: jint, mut f: F) -> Option
+fn parse_marshal_inplace(env: &mut JNIEnv, str: JByteBuffer, limit: jint, mut f: F) -> i32
where
- F: FnMut(&VDom, &Parser, &JnixEnv, &str) -> Option,
+ F: FnMut(&VDom, &Parser, &str) -> Option + 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::() {
+ Some(s) => &s[..],
+ None => "Box",
+ },
+ };
+ env.throw_new("java/lang/RuntimeException", msg).unwrap();
+ 0
+ }
+ }
}
fn get_node_handle_attr<'a>(
diff --git a/app/src/main/rust/src/parser/fav.rs b/app/src/main/rust/src/parser/fav.rs
index dc8bf11de..29697e431 100644
--- a/app/src/main/rust/src/parser/fav.rs
+++ b/app/src/main/rust/src/parser/fav.rs
@@ -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,
+ countArray: Vec,
+ galleryListResult: GalleryListResult,
+}
+
+fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Option {
+ if html.contains("This page requires you to log on.
") {
+ 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::()
+ .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.") {
- panic!("Not logged in!")
- }
- let vec: Vec = 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::()
- .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())
}
diff --git a/app/src/main/rust/src/parser/home.rs b/app/src/main/rust/src/parser/home.rs
index 9fc3d5309..d211c85c3 100644
--- a/app/src/main/rust/src/parser/home.rs
+++ b/app/src/main/rust/src/parser/home.rs
@@ -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 {
+ let iter = get_vdom_first_element_by_class_name(dom, "homebox")?
+ .as_tag()?
+ .query_selector(parser, "strong")?;
+ let vec: Vec = iter
+ .filter_map(|e| e.get(parser)?.inner_text(parser).parse::().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 = iter
- .filter_map(|e| e.get(parser)?.inner_text(parser).parse::().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()
}
diff --git a/app/src/main/rust/src/parser/list.rs b/app/src/main/rust/src/parser/list.rs
index d1fa6add6..c7f0da9fe 100644
--- a/app/src/main/rust/src/parser/list.rs
+++ b/app/src/main/rust/src/parser/list.rs
@@ -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,
}
-#[derive(Default, IntoJava)]
+#[derive(Serialize)]
#[allow(non_snake_case)]
-#[jnix(package = "com.hippo.ehviewer.client.parser")]
pub struct GalleryListResult {
prev: Option,
next: Option,
@@ -193,57 +190,50 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option {
})
}
+pub fn parse_info_list(dom: &VDom, parser: &Parser, str: &str) -> Option {
+ check_html(str);
+ if str.contains("You do not have any watched tags") {
+ panic!("No watched tags!")
+ }
+ if str.contains("No hits found
") || 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 = 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("You do not have any watched tags") {
- panic!("No watched tags!")
- }
- if str.contains("No hits found
") || 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 = 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()
}
diff --git a/app/src/main/rust/src/parser/torrent.rs b/app/src/main/rust/src/parser/torrent.rs
index ec1a4e699..6f1417684 100644
--- a/app/src/main/rust/src/parser/torrent.rs
+++ b/app/src/main/rust/src/parser/torrent.rs
@@ -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> {
+ 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!(" ([0-9-]+) [0-9:]+[\\s\\S]+ ([0-9.]+ [KMGT]iB)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+([^<]+)[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)").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::>();
+ 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!(" ([0-9-]+) [0-9:]+[\\s\\S]+ ([0-9.]+ [KMGT]iB)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+ ([0-9]+)[\\s\\S]+([^<]+)[\\s\\S]+onclick=\"document.location='([^\"]+)'[^<]+>([^<]+)").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::>())
- }).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)
+ })
}