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
This commit is contained in:
Michael Pfaff 2022-03-26 23:06:08 -04:00
parent 5d0243df9a
commit 2aaa043bd2
Signed by: michael
GPG Key ID: F1A27427218FCA77
11 changed files with 353 additions and 234 deletions

View File

@ -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 <dev.patrick.auernig@gmail.com>",
@ -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]]

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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
}
}
}

View File

@ -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<T: Clone> Clone for ConnectionState<T> {
impl<T: Copy> Copy for ConnectionState<T> {}
impl<T> ConnectionState<T> {
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<T> ConnectionState<T> {
pub fn hollow(&self) -> ConnectionState<()> {
match self {
@ -102,19 +72,21 @@ macro_rules! yield_while {
}};
}
type FullConnectionState = ConnectionState<(u64, Mutex<SocketConnection>)>;
#[derive(Debug)]
pub struct Client {
state: (
watch::Sender<ConnectionState<(u64, Mutex<SocketConnection>)>>,
watch::Receiver<ConnectionState<(u64, Mutex<SocketConnection>)>>,
),
state_sender: watch::Sender<FullConnectionState>,
state_receiver: watch::Receiver<FullConnectionState>,
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<u64> {
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<SocketConnection> {
debug!("Connecting");
@ -141,37 +114,44 @@ impl Client {
Ok(new_connection)
}
#[instrument(level = "debug")]
async fn connect0(&self, client_id: u64, conn: Result<SocketConnection>) -> 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<A, E>(&self, cmd: Command, args: A, evt: Option<Event>) -> Result<Payload<E>>
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<E> = 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<E> = serde_json::from_str(&payload)?;
match response.evt {
Some(Event::Error) => Err(Error::SubscriptionFailed),
_ => Ok(response),
}
}

View File

@ -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<Message> {
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 {

View File

@ -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"))
}

View File

@ -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<Self> {
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<()> {

View File

@ -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),

View File

@ -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<tokio::runtime::Runtime> = 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<T, E: core::fmt::Debug>(result: Result<T, E>) -> Result<T, ()> {
result.map_err(|e| {
error!("{:?}", e);
()
})
}
fn is_null<'b, O>(env: JNIEnv, ref1: O) -> jni::errors::Result<bool> where O: Into<JObject<'b>> {
fn is_null<'b, O>(env: JNIEnv, ref1: O) -> jni::errors::Result<bool>
where
O: Into<JObject<'b>>,
{
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<MutexGuard<'a, drpc::Client>, ()> {
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<MutexGuard<'a, drpc::Client>> {
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<JObject<'a>, ()> {
pub fn jni_create<'a>(env: JNIEnv<'a>, _class: JClass) -> Result<JObject<'a>> {
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::<u64>()
)?;
let client_id = env.get_string(client_id)?.to_str()?.parse::<u64>()?;
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<drpc::models::Activity, ()> {
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<drpc::models::Activity> {
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<drpc::models::Ac
}
}
if let JValue::Object(obj) = j_timestamps {
if !is_null(env, obj).map_err(|_| ())? {
if !is_null(env, obj)? {
let timestamps = jobject_to_activity_timestamps(env, obj)?;
activity = activity.timestamps(|_|timestamps);
activity = activity.timestamps(|_| timestamps);
}
}
if let JValue::Object(obj) = j_assets {
if !is_null(env, obj).map_err(|_| ())? {
if !is_null(env, obj)? {
let assets = jobject_to_activity_assets(env, obj)?;
activity = activity.assets(|_|assets);
activity = activity.assets(|_| assets);
}
}
if let JValue::Object(obj) = j_party {
if !is_null(env, obj).map_err(|_| ())? {
if !is_null(env, obj)? {
let party = jobject_to_activity_party(env, obj)?;
activity = activity.party(|_|party);
activity = activity.party(|_| party);
}
}
if let JValue::Object(obj) = j_secrets {
if !is_null(env, obj).map_err(|_| ())? {
if !is_null(env, obj)? {
let secrets = jobject_to_activity_secrets(env, obj)?;
activity = activity.secrets(|_|secrets);
activity = activity.secrets(|_| secrets);
}
}
Ok(activity)
}
fn jobject_to_activity_timestamps(env: JNIEnv, jobject: JObject) -> Result<drpc::models::ActivityTimestamps, ()> {
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<drpc::models::ActivityTimestamps> {
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<drpc:
Ok(timestamps)
}
fn jobject_to_activity_assets(env: JNIEnv, jobject: JObject) -> Result<drpc::models::ActivityAssets, ()> {
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<drpc::models::ActivityAssets> {
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<drpc::models::ActivityParty, ()> {
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<drpc::models::ActivityParty> {
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<drpc::mode
Ok(party)
}
fn jobject_to_activity_secrets(env: JNIEnv, jobject: JObject) -> Result<drpc::models::ActivitySecrets, ()> {
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<drpc::models::ActivitySecrets> {
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)
}

View File

@ -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;