Refine FFI error handling
This commit is contained in:
1 parent
f7a70cdfc3
commit
3f784ec1de
5 files changed
+129
-37
No files matched your search
@@ -31,6 +31,7 @@ import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.fillInfo
|
||||
import com.hippo.ehviewer.client.data.filterComments
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import com.hippo.ehviewer.client.exception.EhError
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.InsufficientFundsException
|
||||
import com.hippo.ehviewer.client.exception.InsufficientGpException
|
||||
@@ -69,6 +70,7 @@ import io.ktor.client.statement.HttpStatement
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.client.statement.request
|
||||
import io.ktor.http.Url
|
||||
import io.ktor.util.moveToByteArray
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import io.ktor.utils.io.pool.useInstance
|
||||
import io.ktor.utils.io.readAvailable
|
||||
@@ -79,6 +81,8 @@ import kotlin.system.measureTimeMillis
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.cbor.Cbor
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.json.add
|
||||
import kotlinx.serialization.json.addJsonArray
|
||||
import kotlinx.serialization.json.put
|
||||
@@ -127,27 +131,30 @@ private fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuff
|
||||
throw IpBannedException(message)
|
||||
}
|
||||
|
||||
// Check parse error thrown by rust
|
||||
val cause = e.cause
|
||||
if (e is ParseException && cause is RuntimeException) {
|
||||
when (val message = cause.message) {
|
||||
"0" -> throw NoHitsFoundException()
|
||||
"1" -> throw EhException(R.string.gallery_list_empty_hit_subscription)
|
||||
"2" -> throw NotLoggedInException()
|
||||
"3" -> throw NoHathClientException()
|
||||
"4" -> throw InsufficientFundsException()
|
||||
is String if message.isNotEmpty() -> {
|
||||
when (message[0]) {
|
||||
'5' -> throw IpBannedException(message.substring(1))
|
||||
'6' -> throw EhException(message.substring(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse error from rust
|
||||
val error = body.getOrNull()?.runCatching {
|
||||
val array = moveToByteArray()
|
||||
rewind()
|
||||
Cbor.decodeFromByteArray<EhError>(array)
|
||||
}?.getOrNull()
|
||||
|
||||
// Check Gallery Not Available - 404
|
||||
if (error is EhError.GalleryUnavailable) throw EhException(error.message)
|
||||
|
||||
// Check bad response code
|
||||
response.status.ensureSuccess()
|
||||
|
||||
when (error) {
|
||||
EhError.NoHits -> throw NoHitsFoundException()
|
||||
EhError.NoWatched -> throw EhException(R.string.gallery_list_empty_hit_subscription)
|
||||
EhError.NeedLogin -> throw NotLoggedInException()
|
||||
EhError.NoHathClient -> throw NoHathClientException()
|
||||
EhError.InsufficientFunds -> throw InsufficientFundsException()
|
||||
is EhError.IpBanned -> throw IpBannedException(error.message)
|
||||
is EhError.Error -> throw EhException(error.message)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
if (e is ParseException || e is SerializationException) {
|
||||
body.onLeft { if ("<" !in it) throw IpBannedException(it) }
|
||||
if (Settings.saveParseErrorBody.value) body.saveParseError(e)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.hippo.ehviewer.client.exception
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.cbor.CborArray
|
||||
|
||||
@Serializable
|
||||
sealed interface EhError {
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("NoHits")
|
||||
data object NoHits : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("NoWatched")
|
||||
data object NoWatched : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("NeedLogin")
|
||||
data object NeedLogin : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("NoHathClient")
|
||||
data object NoHathClient : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("InsufficientFunds")
|
||||
data object InsufficientFunds : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("GalleryUnavailable")
|
||||
data class GalleryUnavailable(val message: String) : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("IpBanned")
|
||||
data class IpBanned(val message: String) : EhError
|
||||
|
||||
@CborArray
|
||||
@Serializable
|
||||
@SerialName("Error")
|
||||
data class Error(val message: String) : EhError
|
||||
}
|
||||
@@ -257,17 +257,28 @@ where
|
||||
{
|
||||
jni_throwing(env, |env| {
|
||||
let buffer = deref_mut_direct_bytebuffer(env, str)?;
|
||||
let value = {
|
||||
// SAFETY: ktor client ensure html content is valid utf-8.
|
||||
let body = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) };
|
||||
f(body)?
|
||||
};
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
serde_cbor::to_writer(&mut cursor, &value)?;
|
||||
Ok(cursor.position() as i32)
|
||||
|
||||
// SAFETY: ktor client ensure html content is valid utf-8.
|
||||
let body = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) };
|
||||
match f(body) {
|
||||
Ok(value) => serialize_to_buffer(buffer, &value),
|
||||
Err(err) => {
|
||||
if let Some(value) = err.downcast_ref::<EhError>() {
|
||||
serialize_to_buffer(buffer, value)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_to_buffer<T: Serialize>(buffer: &mut [u8], value: &T) -> Result<i32> {
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
serde_cbor::to_writer(&mut cursor, value)?;
|
||||
Ok(cursor.position() as i32)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(non_snake_case)]
|
||||
pub extern "system" fn JNI_OnLoad(_: JavaVM, _: *mut c_void) -> jint {
|
||||
|
||||
@@ -5,6 +5,7 @@ mod ffi;
|
||||
pub mod img;
|
||||
pub mod parser;
|
||||
|
||||
use serde::{Serialize, Serializer, ser::SerializeTuple};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use tl::{Bytes, HTMLTag, Node, NodeHandle, Parser, VDom};
|
||||
|
||||
@@ -74,22 +75,47 @@ enum EhError {
|
||||
NeedLogin,
|
||||
NoHathClient,
|
||||
InsufficientFunds,
|
||||
GalleryUnavailable(String),
|
||||
IpBanned(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl EhError {
|
||||
fn variant_name(&self) -> &'static str {
|
||||
match self {
|
||||
EhError::NoHits => "NoHits",
|
||||
EhError::NoWatched => "NoWatched",
|
||||
EhError::NeedLogin => "NeedLogin",
|
||||
EhError::NoHathClient => "NoHathClient",
|
||||
EhError::InsufficientFunds => "InsufficientFunds",
|
||||
EhError::GalleryUnavailable(_) => "GalleryUnavailable",
|
||||
EhError::IpBanned(_) => "IpBanned",
|
||||
EhError::Error(_) => "Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for EhError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut tup = serializer.serialize_tuple(2)?;
|
||||
tup.serialize_element(self.variant_name())?;
|
||||
let payload = match self {
|
||||
EhError::GalleryUnavailable(s) | EhError::IpBanned(s) | EhError::Error(s) => {
|
||||
std::slice::from_ref(s)
|
||||
}
|
||||
_ => &[],
|
||||
};
|
||||
tup.serialize_element(payload)?;
|
||||
tup.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EhError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = match self {
|
||||
EhError::NoHits => "0",
|
||||
EhError::NoWatched => "1",
|
||||
EhError::NeedLogin => "2",
|
||||
EhError::NoHathClient => "3",
|
||||
EhError::InsufficientFunds => "4",
|
||||
EhError::IpBanned(s) => &format!("5{s}"),
|
||||
EhError::Error(s) => &format!("6{s}"),
|
||||
};
|
||||
f.write_str(msg)
|
||||
Debug::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -470,11 +470,11 @@ pub fn parse_pages(dom: &VDom, parser: &Parser) -> Result<i32> {
|
||||
}
|
||||
|
||||
pub fn parse_preview_list(dom: &VDom, parser: &Parser) -> Result<Vec<GalleryPreview>> {
|
||||
// Check Gallery Not Available
|
||||
let error = get_vdom_first_element_by_class_name(dom, "d");
|
||||
if let Some(tag) = error.and_then(|n| get_first_child(n.as_tag()?, parser)) {
|
||||
let node = tag.children().top()[0].get(parser).unwrap();
|
||||
bail!(EhError::Error(node.inner_text(parser).to_string()));
|
||||
let err = EhError::GalleryUnavailable(node.inner_text(parser).to_string());
|
||||
bail!(err);
|
||||
}
|
||||
|
||||
dom.get_element_by_id("gdt")
|
||||
|
||||
Reference in new issue
Block a user