From 2aaa043bd28b5c263463192d3c88fa46c5e342df Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sat, 26 Mar 2022 23:06:08 -0400 Subject: [PATCH] See message - **Breaking change**: Multiple functions renamed in `src/java.rs`. This is not bumping the major version because these functions are considered internal. - Fix disconnecting dead-lock - `Client::execute` now disconnects when `Error::ConnectionClosed` or `Error::ConnectionClosedWhileSending` is encountered - Use `tracing` instead of `log` - Add the `instrument` attribute to most `Client` functions - Remove unnecessary `Arc` from JNI bindings --- Cargo.toml | 14 +- examples/discord_presence.rs | 16 +- examples/discord_presence_subscribe.rs | 12 +- examples/java.rs | 91 ++++++++-- src/client.rs | 167 +++++++++--------- src/connection/base.rs | 38 +++-- src/connection/unix.rs | 5 +- src/connection/windows.rs | 6 +- src/error.rs | 6 +- src/java.rs | 226 ++++++++++++++----------- src/lib.rs | 6 +- 11 files changed, 353 insertions(+), 234 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f97fa64..4e74f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "discord-rpc-client" -version = "0.5.2" +version = "0.5.3" description = "A Rust client for Discord RPC." authors = [ "Patrick Auernig ", @@ -19,25 +19,27 @@ tokio-parking_lot = ["tokio/parking_lot"] java-bindings = ["lazy_static", "jni", "tokio/rt-multi-thread"] [dependencies] +anyhow = "1" async-trait = "0.1.52" +byteorder = "1.0" +bytes = "1.1.0" +const_format = "0.2.22" jni = { version = "0.19", optional = true } lazy_static = { version = "1.4", optional = true } -tokio = { version = "1.16", features = ["io-util", "net", "sync", "macros", "rt"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -byteorder = "1.0" -log = "0.4" -bytes = "1.1.0" +tokio = { version = "1.16", features = ["io-util", "net", "sync", "macros", "rt"] } +tracing = "0.1.32" uuid = { version = "0.8", features = ["v4"] } [dev-dependencies] -simplelog = "0.11" tokio = { version = "1.16", features = [ "time", "rt-multi-thread", "macros", "parking_lot", ] } +tracing-subscriber = "0.3.9" # this is a workaround to not being able to specify either multiple libs or conditional compilation based on crate-type. [[example]] diff --git a/examples/discord_presence.rs b/examples/discord_presence.rs index 86b76d0..30e6964 100644 --- a/examples/discord_presence.rs +++ b/examples/discord_presence.rs @@ -1,16 +1,12 @@ +#[macro_use] +extern crate tracing; + use discord_rpc_client::{models::Activity, Client as DiscordRPC}; -use simplelog::*; use std::io; #[tokio::main] async fn main() -> discord_rpc_client::Result<()> { - TermLogger::init( - LevelFilter::Debug, - Config::default(), - TerminalMode::Mixed, - ColorChoice::Always, - ) - .unwrap(); + tracing_subscriber::fmt::init(); let drpc = DiscordRPC::default(); @@ -24,7 +20,7 @@ async fn main() -> discord_rpc_client::Result<()> { if buf.is_empty() { if let Err(why) = drpc.clear_activity().await { - println!("Failed to clear presence: {}", why); + error!("Failed to clear presence: {}", why); } } else { if let Err(why) = drpc @@ -36,7 +32,7 @@ async fn main() -> discord_rpc_client::Result<()> { })) .await { - println!("Failed to set presence: {}", why); + error!("Failed to set presence: {}", why); } } } diff --git a/examples/discord_presence_subscribe.rs b/examples/discord_presence_subscribe.rs index 691b3a8..9bc5abe 100644 --- a/examples/discord_presence_subscribe.rs +++ b/examples/discord_presence_subscribe.rs @@ -1,16 +1,12 @@ +#[macro_use] +extern crate tracing; + use discord_rpc_client::{models::Event, Client as DiscordRPC}; -use simplelog::*; use std::{thread, time}; #[tokio::main] async fn main() -> discord_rpc_client::Result<()> { - TermLogger::init( - LevelFilter::Debug, - Config::default(), - TerminalMode::Mixed, - ColorChoice::Always, - ) - .unwrap(); + tracing_subscriber::fmt::init(); let drpc = DiscordRPC::default(); diff --git a/examples/java.rs b/examples/java.rs index 65f25ff..d5d71db 100644 --- a/examples/java.rs +++ b/examples/java.rs @@ -1,31 +1,98 @@ +#[macro_use] +extern crate tracing; + use discord_rpc_client::java::*; +use jni::objects::{JClass, JObject, JString}; use jni::JNIEnv; -use jni::objects::{JClass, JString, JObject}; -// can't just put these in src/java.rs and reexport because of some tree-shaking that the compiler does. +// can't just put these in src/java.rs and re-export because of some tree-shaking that the compiler does. #[no_mangle] -pub extern "system" fn Java_com_discord_rpc_DiscordRPC_create<'a>(env: JNIEnv<'a>, class: JClass) -> JObject<'a> { - Java_com_discord_rpc_DiscordRPC_create0(env, class).unwrap_or(JObject::null()) +pub extern "system" fn Java_com_discord_rpc_DiscordRPC_create<'a>( + env: JNIEnv<'a>, + class: JClass, +) -> JObject<'a> { + let _ = tracing_subscriber::fmt::try_init(); + + match jni_create(env, class) { + Ok(obj) => obj, + Err(e) => { + error!( + concat!("at ", file!(), ":", line!(), ":", column!(), ": {}"), + e + ); + JObject::null() + } + } } #[no_mangle] -pub extern "system" fn Java_com_discord_rpc_DiscordRPC_connect(env: JNIEnv, obj: JObject, client_id: JString) -> bool { - Java_com_discord_rpc_DiscordRPC_connect0(env, obj, client_id).is_ok() +pub extern "system" fn Java_com_discord_rpc_DiscordRPC_connect( + env: JNIEnv, + obj: JObject, + client_id: JString, +) -> bool { + match jni_connect(env, obj, client_id) { + Ok(_) => true, + Err(e) => { + error!( + concat!("at ", file!(), ":", line!(), ":", column!(), ": {}"), + e + ); + false + } + } } #[no_mangle] -pub extern "system" fn Java_com_discord_rpc_DiscordRPC_disconnect(env: JNIEnv, obj: JObject) -> bool { - Java_com_discord_rpc_DiscordRPC_disconnect0(env, obj).is_ok() +pub extern "system" fn Java_com_discord_rpc_DiscordRPC_disconnect( + env: JNIEnv, + obj: JObject, +) -> bool { + match jni_disconnect(env, obj) { + Ok(_) => true, + Err(e) => { + error!( + concat!("at ", file!(), ":", line!(), ":", column!(), ": {}"), + e + ); + false + } + } } #[no_mangle] -pub extern "system" fn Java_com_discord_rpc_DiscordRPC_setActivity(env: JNIEnv, obj: JObject, j_activity: JObject) -> bool { - Java_com_discord_rpc_DiscordRPC_setActivity0(env, obj, j_activity).is_ok() +pub extern "system" fn Java_com_discord_rpc_DiscordRPC_setActivity( + env: JNIEnv, + obj: JObject, + j_activity: JObject, +) -> bool { + match jni_set_activity(env, obj, j_activity) { + Ok(_) => true, + Err(e) => { + error!( + concat!("at ", file!(), ":", line!(), ":", column!(), ": {}"), + e + ); + false + } + } } #[no_mangle] -pub extern "system" fn Java_com_discord_rpc_DiscordRPC_clearActivity(env: JNIEnv, obj: JObject) -> bool { - Java_com_discord_rpc_DiscordRPC_clearActivity0(env, obj).is_ok() +pub extern "system" fn Java_com_discord_rpc_DiscordRPC_clearActivity( + env: JNIEnv, + obj: JObject, +) -> bool { + match jni_clear_activity(env, obj) { + Ok(_) => true, + Err(e) => { + error!( + concat!("at ", file!(), ":", line!(), ":", column!(), ": {}"), + e + ); + false + } + } } diff --git a/src/client.rs b/src/client.rs index 1f124ee..7191780 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,8 +2,8 @@ use serde::{de::DeserializeOwned, Serialize}; #[allow(unused)] use serde_json::Value; use tokio::select; -use tokio::sync::watch::Ref; -use tokio::sync::{watch, Mutex}; +use tokio::sync::watch; +use tokio::sync::Mutex; use crate::connection::{Connection, SocketConnection}; use crate::error::{Error, Result}; @@ -46,36 +46,6 @@ impl Clone for ConnectionState { impl Copy for ConnectionState {} -impl ConnectionState { - pub fn is_disconnected(&self) -> bool { - match self { - ConnectionState::Disconnected => true, - _ => false, - } - } - - pub fn is_connecting(&self) -> bool { - match self { - ConnectionState::Connecting => true, - _ => false, - } - } - - pub fn is_connected(&self) -> bool { - match self { - ConnectionState::Connected(_) => true, - _ => false, - } - } - - pub fn is_disconnecting(&self) -> bool { - match self { - ConnectionState::Disconnecting => true, - _ => false, - } - } -} - impl ConnectionState { pub fn hollow(&self) -> ConnectionState<()> { match self { @@ -102,19 +72,21 @@ macro_rules! yield_while { }}; } +type FullConnectionState = ConnectionState<(u64, Mutex)>; + #[derive(Debug)] pub struct Client { - state: ( - watch::Sender)>>, - watch::Receiver)>>, - ), + state_sender: watch::Sender, + state_receiver: watch::Receiver, update: Mutex<()>, } impl Default for Client { fn default() -> Self { + let (state_sender, state_receiver) = watch::channel(ConnectionState::Disconnected); Self { - state: watch::channel(ConnectionState::Disconnected), + state_sender, + state_receiver, update: Mutex::new(()), } } @@ -123,12 +95,13 @@ impl Default for Client { impl Client { /// Returns the client id used by the current connection, or [`None`] if the client is not [`ConnectionState::Connected`]. pub fn client_id(&self) -> Option { - match *self.state.1.borrow() { + match *self.state_receiver.borrow() { ConnectionState::Connected((client_id, _)) => Some(client_id), _ => None, } } + #[instrument(level = "debug")] async fn connect_and_handshake(client_id: u64) -> Result { debug!("Connecting"); @@ -141,37 +114,44 @@ impl Client { Ok(new_connection) } + #[instrument(level = "debug")] async fn connect0(&self, client_id: u64, conn: Result) -> Result<()> { let _state_guard = self.update.lock().await; - match hollow!(self.state.1) { + match hollow!(self.state_receiver) { state @ ConnectionState::Disconnected => panic!( "Illegal state during connection process {:?} -> {:?}", ConnectionState::<()>::Connecting, state ), - ConnectionState::Connecting => { - self.state - .0 - .send(ConnectionState::Connected((client_id, Mutex::new(conn?)))) - .expect("the receiver cannot be dropped without the sender!"); - debug!("Connected"); - Ok(()) - } + ConnectionState::Connecting => match conn { + Ok(conn) => { + self.state_sender + .send(ConnectionState::Connected((client_id, Mutex::new(conn)))) + .expect("the receiver cannot be dropped without the sender!"); + debug!("Connected"); + Ok(()) + } + Err(e) => { + self.state_sender + .send(ConnectionState::Disconnected) + .expect("the receiver cannot be dropped without the sender!"); + debug!("Failed to connect and disconnected"); + Err(e) + } + }, ConnectionState::Connected(_) => panic!("Illegal concurrent connection!"), ConnectionState::Disconnecting => { match conn { - Ok(conn) => match conn.disconnect().await { - Err(e) => { + Ok(conn) => { + if let Err(e) = conn.disconnect().await { error!("failed to disconnect properly: {}", e); } - _ => {} - }, + } Err(e) => { error!("failed connection: {}", e); } } - self.state - .0 + self.state_sender .send(ConnectionState::Disconnected) .expect("the receiver cannot be dropped without the sender!"); Err(Error::ConnectionClosed) @@ -179,16 +159,20 @@ impl Client { } } + #[instrument(level = "info")] pub async fn connect(&self, client_id: u64) -> Result<()> { - match hollow!(self.state.1) { + match hollow!(self.state_receiver) { ConnectionState::Connected(_) => Ok(()), _ => { let state_guard = self.update.lock().await; - match hollow!(self.state.1) { + match hollow!(self.state_receiver) { ConnectionState::Connected(_) => Ok(()), ConnectionState::Disconnecting => Err(Error::ConnectionClosed), ConnectionState::Connecting => { - match yield_while!(hollow!(self.state.1), ConnectionState::Connecting) { + match yield_while!( + hollow!(self.state_receiver), + ConnectionState::Connecting + ) { ConnectionState::Connected(_) => Ok(()), ConnectionState::Disconnecting => Err(Error::ConnectionClosed), ConnectionState::Disconnected => Err(Error::ConnectionClosed), @@ -196,8 +180,7 @@ impl Client { } } ConnectionState::Disconnected => { - self.state - .0 + self.state_sender .send(ConnectionState::Connecting) .expect("the receiver cannot be dropped without the sender!"); @@ -206,8 +189,8 @@ impl Client { conn = Self::connect_and_handshake(client_id) => { self.connect0(client_id, conn).await } - // _ = tokio::task::yield_now() if self.state.1.borrow().is_disconnecting() => { - // self.state.0.send(ConnectionState::Disconnected).expect("the receiver cannot be dropped without the sender!"); + // _ = tokio::task::yield_now() if self.state_receiver.borrow().is_disconnecting() => { + // self.state_sender.send(ConnectionState::Disconnected).expect("the receiver cannot be dropped without the sender!"); // Err(Error::ConnectionClosed) // } } @@ -220,38 +203,52 @@ impl Client { /// If currently connected, the function will close the connection. /// If currently connecting, the function will wait for the connection to be established and will immediately close it. /// If currently disconnecting, the function will wait for the connection to be closed. + #[instrument(level = "info")] pub async fn disconnect(&self) { let _state_guard = self.update.lock().await; - match hollow!(self.state.1) { + trace!("aquired state guard for disconnect"); + match hollow!(self.state_receiver) { ConnectionState::Disconnected => {} ref state @ ConnectionState::Disconnecting => { - match yield_while!(hollow!(self.state.1), ConnectionState::Disconnecting) { + trace!("Waiting while in disconnecting state(b)"); + match yield_while!(hollow!(self.state_receiver), ConnectionState::Disconnecting) { ConnectionState::Disconnected => {} ConnectionState::Disconnecting => unreachable!(), new_state => panic!("Illegal state change {:?} -> {:?}", state, new_state), } } ConnectionState::Connecting => { - self.state - .0 + self.state_sender .send(ConnectionState::Disconnecting) .expect("the receiver cannot be dropped without the sender!"); } - state @ ConnectionState::Connected(_) => { - match self.state.0.send_replace(ConnectionState::Disconnecting) { + state @ ConnectionState::Connected(()) => { + trace!("Sending disconnecting state"); + let s = self + .state_sender + .send_replace(ConnectionState::Disconnecting); + trace!("Sent disconnecting state"); + match s { ConnectionState::Connected(conn) => { match conn.1.into_inner().disconnect().await { Err(e) => { error!("failed to disconnect properly: {}", e); } - _ => {} + _ => self + .state_sender + .send(ConnectionState::Disconnected) + .expect("the receiver cannot be dropped without the sender!"), } } new_state @ ConnectionState::Connecting => { panic!("Illegal state change {:?} -> {:?}", state, new_state); } state @ ConnectionState::Disconnecting => { - match yield_while!(hollow!(self.state.1), ConnectionState::Disconnecting) { + trace!("Waiting while in disconnecting state(b)"); + match yield_while!( + hollow!(self.state_receiver), + ConnectionState::Disconnecting + ) { ConnectionState::Disconnected => {} ConnectionState::Disconnecting => unreachable!(), new_state => { @@ -265,27 +262,39 @@ impl Client { } } + #[instrument(level = "info")] async fn execute(&self, cmd: Command, args: A, evt: Option) -> Result> where - A: Serialize + Send + Sync, - E: Serialize + DeserializeOwned + Send + Sync, + A: std::fmt::Debug + Serialize + Send + Sync, + E: std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync, { let message = Message::new( OpCode::Frame, Payload::with_nonce(cmd, Some(args), None, evt), ); - match *self.state.1.borrow() { - ConnectionState::Connected((_, ref conn)) => { - let mut conn = conn.lock().await; - conn.send(message).await?; - let Message { payload, .. } = conn.recv().await?; - let response: Payload = serde_json::from_str(&payload)?; - match response.evt { - Some(Event::Error) => Err(Error::SubscriptionFailed), - _ => Ok(response), + let result = match &*self.state_receiver.borrow() { + ConnectionState::Connected((_, conn)) => { + try { + let mut conn = conn.lock().await; + conn.send(message).await?; + conn.recv().await? } } _ => Err(Error::ConnectionClosed), + }; + let Message { payload, .. } = match result { + Ok(msg) => Ok(msg), + Err(e @ Error::ConnectionClosed | e @ Error::ConnectionClosedWhileSending(_)) => { + debug!("disconnecting because connection is closed."); + self.disconnect().await; + Err(e) + } + Err(e) => Err(e), + }?; + let response: Payload = serde_json::from_str(&payload)?; + match response.evt { + Some(Event::Error) => Err(Error::SubscriptionFailed), + _ => Ok(response), } } diff --git a/src/connection/base.rs b/src/connection/base.rs index 4d775fe..15dda33 100644 --- a/src/connection/base.rs +++ b/src/connection/base.rs @@ -1,10 +1,7 @@ -use std::{ - marker::Sized, - path::PathBuf, -}; +use std::{marker::Sized, path::PathBuf}; use bytes::BytesMut; -use tokio::io::{AsyncWrite, AsyncRead, AsyncWriteExt, AsyncReadExt}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::error::{Error, Result}; use crate::models::message::{Message, OpCode}; @@ -33,7 +30,8 @@ pub trait Connection: Sized + Send { "nonce": utils::nonce() }]; - self.send(Message::new(OpCode::Handshake, hs.clone())).await?; + self.send(Message::new(OpCode::Handshake, hs.clone())) + .await?; self.recv().await?; Ok(()) @@ -47,20 +45,36 @@ pub trait Connection: Sized + Send { } async fn send(&mut self, message: Message) -> Result<()> { - match message.encode() { - Err(why) => error!("{:?}", why), - Ok(bytes) => { - self.socket().write_all(bytes.as_ref()).await?; + let bytes = message.encode()?; + match self.socket().write_all(bytes.as_ref()).await { + Ok(()) => {} + Err(e) => { + return match e.kind() { + std::io::ErrorKind::BrokenPipe => { + Err(Error::ConnectionClosedWhileSending(message)) + } + _ => Err(e.into()), + } } - }; + } + debug!("-> {:?}", message); + Ok(()) } async fn recv(&mut self) -> Result { let mut buf = BytesMut::new(); buf.resize(1024, 0); - let n = self.socket().read(&mut buf).await?; + let n = match self.socket().read(&mut buf).await { + Ok(n) => n, + Err(e) => { + return match e.kind() { + std::io::ErrorKind::BrokenPipe => Err(Error::ConnectionClosed), + _ => Err(e.into()), + } + } + }; debug!("Received {} bytes", n); if n == 0 { diff --git a/src/connection/unix.rs b/src/connection/unix.rs index e357368..6d45f1a 100644 --- a/src/connection/unix.rs +++ b/src/connection/unix.rs @@ -31,10 +31,7 @@ impl Connection for UnixConnection { p }) .or_else(|| env::var_os("TMPDIR").map(PathBuf::from)) - .or_else(|| match env::temp_dir().to_str() { - None => None, - Some(tmp) => Some(PathBuf::from(tmp)), - }) + .or_else(|| env::temp_dir().to_str().map(PathBuf::from)) .unwrap_or_else(|| PathBuf::from("/tmp")) } diff --git a/src/connection/windows.rs b/src/connection/windows.rs index 7ba10ad..ac2aead 100644 --- a/src/connection/windows.rs +++ b/src/connection/windows.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, time}; +use std::path::PathBuf; use tokio::net::windows::named_pipe::{ClientOptions, NamedPipeClient}; @@ -24,7 +24,9 @@ impl Connection for WindowsConnection { async fn connect() -> Result { let connection_name = Self::socket_path(0); - Ok(Self { socket: ClientOptions::new().open(connection_name)? }) + Ok(Self { + socket: ClientOptions::new().open(connection_name)?, + }) } async fn disconnect(mut self) -> Result<()> { diff --git a/src/error.rs b/src/error.rs index f095041..1283b5f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ use serde_json::Error as JsonError; -use tokio::sync::mpsc::error::SendError; use std::{ error::Error as StdError, fmt::{self, Display, Formatter}, @@ -7,6 +6,7 @@ use std::{ result::Result as StdResult, sync::mpsc::RecvTimeoutError as ChannelTimeout, }; +use tokio::sync::mpsc::error::SendError; use crate::models::Message; @@ -28,7 +28,9 @@ impl Display for Error { Self::Conversion => f.write_str("Failed to convert values"), Self::SubscriptionFailed => f.write_str("Failed to subscribe to event"), Self::ConnectionClosed => f.write_str("Connection closed"), - Self::ConnectionClosedWhileSending(msg) => write!(f, "Connection closed while sending {:?}", msg), + Self::ConnectionClosedWhileSending(msg) => { + write!(f, "Connection closed while sending {:?}", msg) + } Self::Busy => f.write_str("A resource was busy"), Self::IoError(err) => write!(f, "{}", err), Self::JsonError(err) => write!(f, "{}", err), diff --git a/src/java.rs b/src/java.rs index c20a0a7..acd2b2b 100644 --- a/src/java.rs +++ b/src/java.rs @@ -1,68 +1,76 @@ -use log::{log, log_enabled, trace, debug, info, warn, error}; +#![allow(clippy::await_holding_lock)] -use std::sync::{Arc, MutexGuard}; +use std::sync::MutexGuard; -use jni::JNIEnv; -use jni::objects::{JClass, JString, JObject, JValue}; +use anyhow::Result; +use jni::objects::{JClass, JObject, JString, JValue}; use jni::signature::{JavaType, Primitive}; -use jni::sys::jstring; +use jni::JNIEnv; pub use jni; use crate as drpc; -const SIG_BOOL: &'static str = "Z"; -const SIG_INT: &'static str = "I"; -const SIG_LONG: &'static str = "J"; -const SIG_NULLABLE_LONG: &'static str = "Ljava/lang/Long;"; -const SIG_STRING: &'static str = "Ljava/lang/String;"; +mod jvm_types { + pub const BOOLEAN: &str = "java/lang/Boolean"; + pub const INTEGER: &str = "java/lang/Integer"; + pub const LONG: &str = "java/lang/Long"; + pub const STRING: &str = "java/lang/String"; +} -const NAME_DISCORD_RPC: &'static str = "com/discord/rpc/DiscordRPC"; +macro_rules! signature { + (bool) => { + "Z" + }; + (i32) => { + "I" + }; + (i64) => { + "J" + }; + (class $path:expr) => { + const_format::formatcp!("L{};", $path) + }; +} + +const PATH_DISCORD_RPC: &str = "com/discord/rpc/DiscordRPC"; lazy_static::lazy_static! { - static ref RUNTIME: Arc = Arc::new(tokio::runtime::Runtime::new().expect("unable to create tokio runtime")); + static ref RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new().expect("unable to create tokio runtime"); } -fn debug_and_discard_err(result: Result) -> Result { - result.map_err(|e| { - error!("{:?}", e); - () - }) -} - -fn is_null<'b, O>(env: JNIEnv, ref1: O) -> jni::errors::Result where O: Into> { +fn is_null<'b, O>(env: JNIEnv, ref1: O) -> jni::errors::Result +where + O: Into>, +{ env.is_same_object(ref1, JObject::null()) } // taking the [`JNIEnv`] as a reference is needed to satisfy the lifetime checker. -fn get_client<'a>(env: &'a JNIEnv<'a>, obj: JObject<'a>) -> Result, ()> { - if !debug_and_discard_err(env.is_instance_of(obj, NAME_DISCORD_RPC))? { - error!("not an instance of DiscordRPC"); - return Err(()) +fn get_client<'a>(env: &'a JNIEnv<'a>, obj: JObject<'a>) -> Result> { + if !env.is_instance_of(obj, PATH_DISCORD_RPC)? { + bail!("not an instance of DiscordRPC"); } - let client = env.get_rust_field::<_, _, drpc::Client>(obj, "handle"); - debug_and_discard_err(client) + let client = env.get_rust_field::<_, _, drpc::Client>(obj, "handle")?; + Ok(client) } +// TODO: method to destory afterwards. #[inline(always)] -pub fn Java_com_discord_rpc_DiscordRPC_create0<'a>(env: JNIEnv<'a>, _class: JClass) -> Result, ()> { +pub fn jni_create<'a>(env: JNIEnv<'a>, _class: JClass) -> Result> { let client = drpc::Client::default(); - let jobj = debug_and_discard_err(env.alloc_object(NAME_DISCORD_RPC))?; - debug_and_discard_err(env.set_rust_field(jobj, "handle", client))?; + let jobj = env.alloc_object(PATH_DISCORD_RPC)?; + env.set_rust_field(jobj, "handle", client)?; Ok(jobj) } #[inline(always)] -pub fn Java_com_discord_rpc_DiscordRPC_connect0(env: JNIEnv, obj: JObject, client_id: JString) -> Result<(), ()> { +pub fn jni_connect(env: JNIEnv, obj: JObject, client_id: JString) -> Result<()> { let client = get_client(&env, obj)?; - let client_id = debug_and_discard_err( - debug_and_discard_err( - debug_and_discard_err(env.get_string(client_id))?.to_str() - )?.parse::() - )?; + let client_id = env.get_string(client_id)?.to_str()?.parse::()?; if let Some(current_client_id) = client.client_id() { if current_client_id != client_id { @@ -70,12 +78,12 @@ pub fn Java_com_discord_rpc_DiscordRPC_connect0(env: JNIEnv, obj: JObject, clien } } - debug_and_discard_err(RUNTIME.block_on(async { client.connect(client_id).await }))?; + RUNTIME.block_on(async { client.connect(client_id).await })?; Ok(()) } #[inline(always)] -pub fn Java_com_discord_rpc_DiscordRPC_disconnect0(env: JNIEnv, obj: JObject) -> Result<(), ()> { +pub fn jni_disconnect(env: JNIEnv, obj: JObject) -> Result<()> { let client = get_client(&env, obj)?; RUNTIME.block_on(async { client.disconnect().await }); @@ -83,40 +91,44 @@ pub fn Java_com_discord_rpc_DiscordRPC_disconnect0(env: JNIEnv, obj: JObject) -> } #[inline(always)] -pub fn Java_com_discord_rpc_DiscordRPC_setActivity0(env: JNIEnv, obj: JObject, j_activity: JObject) -> Result<(), ()> { +pub fn jni_set_activity(env: JNIEnv, obj: JObject, j_activity: JObject) -> Result<()> { let client = get_client(&env, obj)?; - + let activity = jobject_to_activity(env, j_activity)?; - debug_and_discard_err(RUNTIME.block_on(async { client.set_activity(activity).await }))?; + RUNTIME.block_on(async { client.set_activity(activity).await })?; Ok(()) } #[inline(always)] -pub fn Java_com_discord_rpc_DiscordRPC_clearActivity0(env: JNIEnv, obj: JObject) -> Result<(), ()> { +pub fn jni_clear_activity(env: JNIEnv, obj: JObject) -> Result<()> { let client = get_client(&env, obj)?; - - debug_and_discard_err(RUNTIME.block_on(async { client.clear_activity().await }))?; + + RUNTIME.block_on(async { client.clear_activity().await })?; Ok(()) } -fn jobject_to_activity(env: JNIEnv, jobject: JObject) -> Result { - let j_state = env.get_field(jobject, "state", SIG_STRING).map_err(|_| ())?; - let j_details = env.get_field(jobject, "details", SIG_STRING).map_err(|_| ())?; - let j_instance = env.get_field(jobject, "instance", SIG_BOOL).map_err(|_| ())?; - let j_timestamps = env.get_field(jobject, "timestamps", "Lcom/discord/rpc/ActivityTimestamps;").map_err(|_| ())?; - let j_assets = env.get_field(jobject, "assets", "Lcom/discord/rpc/ActivityAssets;").map_err(|_| ())?; - let j_party = env.get_field(jobject, "party", "Lcom/discord/rpc/ActivityParty;").map_err(|_| ())?; - let j_secrets = env.get_field(jobject, "secrets", "Lcom/discord/rpc/ActivitySecrets;").map_err(|_| ())?; +fn jobject_to_activity(env: JNIEnv, jobject: JObject) -> Result { + let j_state = env.get_field(jobject, "state", signature!(class jvm_types::STRING))?; + let j_details = env.get_field(jobject, "details", signature!(class jvm_types::STRING))?; + let j_instance = env.get_field(jobject, "instance", signature!(bool))?; + let j_timestamps = env.get_field( + jobject, + "timestamps", + signature!(class "com/discord/rpc/ActivityTimestamps"), + )?; + let j_assets = env.get_field(jobject, "assets", signature!(class "com/discord/rpc/ActivityAssets"))?; + let j_party = env.get_field(jobject, "party", signature!(class "com/discord/rpc/ActivityParty"))?; + let j_secrets = env.get_field(jobject, "secrets", signature!(class "com/discord/rpc/ActivitySecrets"))?; let mut activity = drpc::models::Activity::new(); if let JValue::Object(obj) = j_state { - if !is_null(env, obj).map_err(|_| ())? { - activity = activity.state(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + activity = activity.state(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_details { - if !is_null(env, obj).map_err(|_| ())? { - activity = activity.details(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + activity = activity.details(env.get_string(obj.into())?); } } if let JValue::Bool(b) = j_instance { @@ -125,48 +137,61 @@ fn jobject_to_activity(env: JNIEnv, jobject: JObject) -> Result Result { - let j_start = debug_and_discard_err(env.get_field(jobject, "start", SIG_NULLABLE_LONG))?; - let j_end = debug_and_discard_err(env.get_field(jobject, "end", SIG_NULLABLE_LONG))?; +fn jobject_to_activity_timestamps( + env: JNIEnv, + jobject: JObject, +) -> Result { + let j_start = env.get_field(jobject, "start", signature!(class jvm_types::LONG))?; + let j_end = env.get_field(jobject, "end", signature!(class jvm_types::LONG))?; let mut timestamps = drpc::models::ActivityTimestamps::new(); if let JValue::Object(obj) = j_start { - if !is_null(env, obj).map_err(|_| ())? { - if let JValue::Long(l) = debug_and_discard_err(env.call_method_unchecked(obj, (obj, "longValue", "()J"), JavaType::Primitive(Primitive::Long), &[]))? { + if !is_null(env, obj)? { + if let JValue::Long(l) = env.call_method_unchecked( + obj, + (obj, "longValue", "()J"), + JavaType::Primitive(Primitive::Long), + &[], + )? { timestamps = timestamps.start(l as u64); } } } if let JValue::Object(obj) = j_end { - if !is_null(env, obj).map_err(|_| ())? { - if let JValue::Long(l) = debug_and_discard_err(env.call_method_unchecked(obj, (obj, "longValue", "()J"), JavaType::Primitive(Primitive::Long), &[]))? { + if !is_null(env, obj)? { + if let JValue::Long(l) = env.call_method_unchecked( + obj, + (obj, "longValue", "()J"), + JavaType::Primitive(Primitive::Long), + &[], + )? { timestamps = timestamps.end(l as u64); } } @@ -175,41 +200,44 @@ fn jobject_to_activity_timestamps(env: JNIEnv, jobject: JObject) -> Result Result { - let j_lrg_img = env.get_field(jobject, "largeImage", SIG_STRING).map_err(|_| ())?; - let j_lrg_txt = env.get_field(jobject, "largeText", SIG_STRING).map_err(|_| ())?; - let j_sml_img = env.get_field(jobject, "smallImage", SIG_STRING).map_err(|_| ())?; - let j_sml_txt = env.get_field(jobject, "smallText", SIG_STRING).map_err(|_| ())?; +fn jobject_to_activity_assets( + env: JNIEnv, + jobject: JObject, +) -> Result { + let j_lrg_img = env.get_field(jobject, "largeImage", signature!(class jvm_types::STRING))?; + let j_lrg_txt = env.get_field(jobject, "largeText", signature!(class jvm_types::STRING))?; + let j_sml_img = env.get_field(jobject, "smallImage", signature!(class jvm_types::STRING))?; + let j_sml_txt = env.get_field(jobject, "smallText", signature!(class jvm_types::STRING))?; let mut assets = drpc::models::ActivityAssets::new(); if let JValue::Object(obj) = j_lrg_img { - if !is_null(env, obj).map_err(|_| ())? { - assets = assets.large_image(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + assets = assets.large_image(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_lrg_txt { - if !is_null(env, obj).map_err(|_| ())? { - assets = assets.large_text(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + assets = assets.large_text(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_sml_img { - if !is_null(env, obj).map_err(|_| ())? { - assets = assets.small_image(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + assets = assets.small_image(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_sml_txt { - if !is_null(env, obj).map_err(|_| ())? { - assets = assets.small_text(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + assets = assets.small_text(env.get_string(obj.into())?); } } Ok(assets) } -fn jobject_to_activity_party(env: JNIEnv, jobject: JObject) -> Result { - let j_id = env.get_field(jobject, "id", SIG_INT).map_err(|_| ())?; - let j_min_size = env.get_field(jobject, "minSize", SIG_INT).map_err(|_| ())?; - let j_max_size = env.get_field(jobject, "maxSize", SIG_INT).map_err(|_| ())?; +fn jobject_to_activity_party(env: JNIEnv, jobject: JObject) -> Result { + let j_id = env.get_field(jobject, "id", signature!(i32))?; + let j_min_size = env.get_field(jobject, "minSize", signature!(i32))?; + let j_max_size = env.get_field(jobject, "maxSize", signature!(i32))?; let mut party = drpc::models::ActivityParty::new(); if let JValue::Int(l) = j_id { @@ -222,28 +250,30 @@ fn jobject_to_activity_party(env: JNIEnv, jobject: JObject) -> Result Result { - let j_join = env.get_field(jobject, "join", SIG_STRING).map_err(|_| ())?; - let j_spectate = env.get_field(jobject, "spectate", SIG_STRING).map_err(|_| ())?; - let j_game = env.get_field(jobject, "game", SIG_STRING).map_err(|_| ())?; +fn jobject_to_activity_secrets( + env: JNIEnv, + jobject: JObject, +) -> Result { + let j_join = env.get_field(jobject, "join", signature!(class jvm_types::STRING))?; + let j_spectate = env.get_field(jobject, "spectate", signature!(class jvm_types::STRING))?; + let j_game = env.get_field(jobject, "game", signature!(class jvm_types::STRING))?; let mut secrets = drpc::models::ActivitySecrets::new(); if let JValue::Object(obj) = j_join { - if !is_null(env, obj).map_err(|_| ())? { - secrets = secrets.join(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + secrets = secrets.join(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_spectate { - if !is_null(env, obj).map_err(|_| ())? { - secrets = secrets.spectate(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + secrets = secrets.spectate(env.get_string(obj.into())?); } } if let JValue::Object(obj) = j_game { - if !is_null(env, obj).map_err(|_| ())? { - secrets = secrets.game(env.get_string(obj.into()).map_err(|_| ())?); + if !is_null(env, obj)? { + secrets = secrets.game(env.get_string(obj.into())?); } } Ok(secrets) } - diff --git a/src/lib.rs b/src/lib.rs index f58231c..8fed09a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ +#![feature(try_blocks)] + #[macro_use] -extern crate log; +extern crate anyhow; #[macro_use] extern crate serde; #[macro_use] extern crate serde_json; +#[macro_use] +extern crate tracing; #[macro_use] mod macros;