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:
parent
5d0243df9a
commit
2aaa043bd2
14
Cargo.toml
14
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 <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]]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
167
src/client.rs
167
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<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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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),
|
||||
|
|
226
src/java.rs
226
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<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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue