Overhaul (part 1)

This commit is contained in:
Michael Pfaff 2022-02-03 09:52:53 -05:00
parent 6ea1c93bae
commit 326354d3b4
Signed by: michael
GPG Key ID: F1A27427218FCA77
19 changed files with 329 additions and 297 deletions

View File

@ -1,17 +1,12 @@
[package] [package]
authors = ["Patrick Auernig <dev.patrick.auernig@gmail.com>"] authors = ["Patrick Auernig <dev.patrick.auernig@gmail.com>", "Michael Pfaff <michael@pfaff.dev>"]
name = "discord-rpc-client" name = "discord-rpc-client"
description = "A Rust client for Discord RPC." description = "A Rust client for Discord RPC."
keywords = ["discord", "rpc", "ipc"] keywords = ["discord", "rpc", "ipc"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
repository = "https://gitlab.com/valeth/discord-rpc-client.rs.git" repository = "https://gitlab.com/valeth/discord-rpc-client.rs.git"
version = "0.3.0" version = "0.4.0"
[badges]
travis-ci = { repository = "valeth/discord-rpc-client.rs" }
appveyor = { repository = "valeth/discord-rpc-client.rs", service = "gitlab" }
maintenance = { status = "experimental" }
[dependencies] [dependencies]
serde = "^1.0" serde = "^1.0"

View File

@ -1,16 +1,16 @@
extern crate simplelog;
extern crate discord_rpc_client; extern crate discord_rpc_client;
extern crate simplelog;
use std::io; use discord_rpc_client::{models::Activity, Client as DiscordRPC};
use simplelog::*; use simplelog::*;
use discord_rpc_client::Client as DiscordRPC; use std::io;
fn main() { fn main() -> discord_rpc_client::Result<()> {
TermLogger::init(LevelFilter::Debug, Config::default()).unwrap(); TermLogger::init(LevelFilter::Debug, Config::default()).unwrap();
let mut drpc = DiscordRPC::new(425407036495495169); let mut drpc = DiscordRPC::new(425407036495495169);
drpc.start(); drpc.connect()?;
loop { loop {
let mut buf = String::new(); let mut buf = String::new();
@ -23,16 +23,14 @@ fn main() {
println!("Failed to clear presence: {}", why); println!("Failed to clear presence: {}", why);
} }
} else { } else {
if let Err(why) = drpc.set_activity(|a| a if let Err(why) = drpc.set_activity(Activity::new().state(buf).assets(|ass| {
.state(buf) ass.large_image("ferris_wat")
.assets(|ass| ass
.large_image("ferris_wat")
.large_text("wat.") .large_text("wat.")
.small_image("rusting") .small_image("rusting")
.small_text("rusting..."))) .small_text("rusting...")
{ })) {
println!("Failed to set presence: {}", why); println!("Failed to set presence: {}", why);
} }
} }
}; }
} }

View File

@ -1,26 +1,21 @@
extern crate simplelog;
extern crate discord_rpc_client; extern crate discord_rpc_client;
extern crate simplelog;
use std::{thread, time}; use discord_rpc_client::{models::Event, Client as DiscordRPC};
use simplelog::*; use simplelog::*;
use discord_rpc_client::{ use std::{thread, time};
Client as DiscordRPC,
models::Event,
};
fn main() { fn main() -> discord_rpc_client::Result<()> {
TermLogger::init(LevelFilter::Debug, Config::default()).unwrap(); TermLogger::init(LevelFilter::Debug, Config::default()).unwrap();
let mut drpc = DiscordRPC::new(425407036495495169); let mut drpc = DiscordRPC::new(425407036495495169);
drpc.start(); drpc.connect()?;
drpc.subscribe(Event::ActivityJoin, |j| j drpc.subscribe(Event::ActivityJoin, |j| j.secret("123456"))
.secret("123456"))
.expect("Failed to subscribe to event"); .expect("Failed to subscribe to event");
drpc.subscribe(Event::ActivitySpectate, |s| s drpc.subscribe(Event::ActivitySpectate, |s| s.secret("123456"))
.secret("123456"))
.expect("Failed to subscribe to event"); .expect("Failed to subscribe to event");
drpc.subscribe(Event::ActivityJoinRequest, |s| s) drpc.subscribe(Event::ActivityJoinRequest, |s| s)
@ -29,5 +24,7 @@ fn main() {
drpc.unsubscribe(Event::ActivityJoinRequest, |j| j) drpc.unsubscribe(Event::ActivityJoinRequest, |j| j)
.expect("Failed to unsubscribe from event"); .expect("Failed to unsubscribe from event");
loop { thread::sleep(time::Duration::from_millis(500)); } loop {
thread::sleep(time::Duration::from_millis(500));
}
} }

View File

@ -1,27 +1,22 @@
use serde::{Serialize, de::DeserializeOwned}; use serde::{de::DeserializeOwned, Serialize};
#[allow(unused)] #[allow(unused)]
use serde_json::Value; use serde_json::Value;
use connection::Manager as ConnectionManager; use connection::Manager as ConnectionManager;
use models::{ use error::{Error, Result};
OpCode,
Command,
Event,
payload::Payload,
message::Message,
commands::{SubscriptionArgs, Subscription},
};
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
use models::rich_presence::{ use models::rich_presence::{
SetActivityArgs, Activity, CloseActivityRequestArgs, SendActivityJoinInviteArgs, SetActivityArgs,
Activity, };
SendActivityJoinInviteArgs, use models::{
CloseActivityRequestArgs, commands::{Subscription, SubscriptionArgs},
message::Message,
payload::Payload,
Command, Event, OpCode,
}; };
use error::{Result, Error};
#[derive(Clone, Debug)]
#[derive(Clone)] #[repr(transparent)]
pub struct Client { pub struct Client {
connection_manager: ConnectionManager, connection_manager: ConnectionManager,
} }
@ -32,30 +27,40 @@ impl Client {
Self { connection_manager } Self { connection_manager }
} }
pub fn start(&mut self) { pub fn client_id(&self) -> u64 {
self.connection_manager.start(); self.connection_manager.client_id()
}
pub fn connect(&mut self) -> Result<()> {
self.connection_manager.connect()
}
pub fn disconnect(&mut self) {
self.connection_manager.disconnect()
} }
fn execute<A, E>(&mut self, cmd: Command, args: A, evt: Option<Event>) -> Result<Payload<E>> fn execute<A, E>(&mut self, cmd: Command, args: A, evt: Option<Event>) -> Result<Payload<E>>
where A: Serialize + Send + Sync, where
E: Serialize + DeserializeOwned + Send + Sync A: Serialize + Send + Sync,
E: Serialize + DeserializeOwned + Send + Sync,
{ {
let message = Message::new(OpCode::Frame, Payload::with_nonce(cmd, Some(args), None, evt)); let message = Message::new(
OpCode::Frame,
Payload::with_nonce(cmd, Some(args), None, evt),
);
self.connection_manager.send(message)?; self.connection_manager.send(message)?;
let Message { payload, .. } = self.connection_manager.recv()?; let Message { payload, .. } = self.connection_manager.recv()?;
let response: Payload<E> = serde_json::from_str(&payload)?; let response: Payload<E> = serde_json::from_str(&payload)?;
match response.evt { match response.evt {
Some(Event::Error) => Err(Error::SubscriptionFailed), Some(Event::Error) => Err(Error::SubscriptionFailed),
_ => Ok(response) _ => Ok(response),
} }
} }
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
pub fn set_activity<F>(&mut self, f: F) -> Result<Payload<Activity>> pub fn set_activity(&mut self, activity: Activity) -> Result<Payload<Activity>> {
where F: FnOnce(Activity) -> Activity self.execute(Command::SetActivity, SetActivityArgs::new(activity), None)
{
self.execute(Command::SetActivity, SetActivityArgs::new(f), None)
} }
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
@ -68,22 +73,32 @@ impl Client {
// they are not documented. // they are not documented.
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
pub fn send_activity_join_invite(&mut self, user_id: u64) -> Result<Payload<Value>> { pub fn send_activity_join_invite(&mut self, user_id: u64) -> Result<Payload<Value>> {
self.execute(Command::SendActivityJoinInvite, SendActivityJoinInviteArgs::new(user_id), None) self.execute(
Command::SendActivityJoinInvite,
SendActivityJoinInviteArgs::new(user_id),
None,
)
} }
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
pub fn close_activity_request(&mut self, user_id: u64) -> Result<Payload<Value>> { pub fn close_activity_request(&mut self, user_id: u64) -> Result<Payload<Value>> {
self.execute(Command::CloseActivityRequest, CloseActivityRequestArgs::new(user_id), None) self.execute(
Command::CloseActivityRequest,
CloseActivityRequestArgs::new(user_id),
None,
)
} }
pub fn subscribe<F>(&mut self, evt: Event, f: F) -> Result<Payload<Subscription>> pub fn subscribe<F>(&mut self, evt: Event, f: F) -> Result<Payload<Subscription>>
where F: FnOnce(SubscriptionArgs) -> SubscriptionArgs where
F: FnOnce(SubscriptionArgs) -> SubscriptionArgs,
{ {
self.execute(Command::Subscribe, f(SubscriptionArgs::new()), Some(evt)) self.execute(Command::Subscribe, f(SubscriptionArgs::new()), Some(evt))
} }
pub fn unsubscribe<F>(&mut self, evt: Event, f: F) -> Result<Payload<Subscription>> pub fn unsubscribe<F>(&mut self, evt: Event, f: F) -> Result<Payload<Subscription>>
where F: FnOnce(SubscriptionArgs) -> SubscriptionArgs where
F: FnOnce(SubscriptionArgs) -> SubscriptionArgs,
{ {
self.execute(Command::Unsubscribe, f(SubscriptionArgs::new()), Some(evt)) self.execute(Command::Unsubscribe, f(SubscriptionArgs::new()), Some(evt))
} }

View File

@ -1,17 +1,15 @@
use std::{ use std::{
io::{Write, Read, ErrorKind}, io::{ErrorKind, Read, Write},
marker::Sized, marker::Sized,
path::PathBuf, path::PathBuf,
thread, thread, time,
time,
}; };
use bytes::BytesMut; use bytes::BytesMut;
use utils;
use models::message::{Message, OpCode};
use error::{Error, Result}; use error::{Error, Result};
use models::message::{Message, OpCode};
use utils;
/// Wait for a non-blocking connection until it's complete. /// Wait for a non-blocking connection until it's complete.
macro_rules! try_until_done { macro_rules! try_until_done {
@ -28,7 +26,6 @@ macro_rules! try_until_done {
} }
} }
pub trait Connection: Sized { pub trait Connection: Sized {
type Socket: Write + Read; type Socket: Write + Read;

View File

@ -1,71 +1,77 @@
use std::{ use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError};
thread, use parking_lot::{RwLock, RwLockUpgradableReadGuard};
sync::{ use std::{io::ErrorKind, sync::Arc, thread, time};
Arc,
},
time,
io::ErrorKind
};
use crossbeam_channel::{unbounded, Receiver, Sender};
use parking_lot::Mutex;
use super::{ use super::{Connection, SocketConnection};
Connection, use error::{Error, Result};
SocketConnection,
};
use models::Message; use models::Message;
use error::{Result, Error};
type Tx = Sender<Message>; type Tx = Sender<Message>;
type Rx = Receiver<Message>; type Rx = Receiver<Message>;
#[derive(Clone)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ConnectionState {
Disconnected,
Connecting,
Connected,
Disconnecting,
}
#[derive(Clone, Debug)]
pub struct Manager { pub struct Manager {
connection: Arc<Option<Mutex<SocketConnection>>>, state: Arc<RwLock<ConnectionState>>,
client_id: u64, client_id: u64,
outbound: (Rx, Tx), outbound: (Rx, Tx),
inbound: (Rx, Tx), inbound: (Rx, Tx),
handshake_completed: bool,
} }
impl Manager { impl Manager {
pub fn new(client_id: u64) -> Self { pub fn new(client_id: u64) -> Self {
let connection = Arc::new(None);
let (sender_o, receiver_o) = unbounded(); let (sender_o, receiver_o) = unbounded();
let (sender_i, receiver_i) = unbounded(); let (sender_i, receiver_i) = unbounded();
Self { Self {
connection, state: Arc::new(RwLock::new(ConnectionState::Disconnected)),
client_id, client_id,
handshake_completed: false,
inbound: (receiver_i, sender_i), inbound: (receiver_i, sender_i),
outbound: (receiver_o, sender_o), outbound: (receiver_o, sender_o),
} }
} }
pub fn start(&mut self) { pub fn client_id(&self) -> u64 {
let manager_inner = self.clone(); self.client_id
thread::spawn(move || {
send_and_receive_loop(manager_inner);
});
} }
pub fn send(&self, message: Message) -> Result<()> { pub fn send(&self, message: Message) -> Result<()> {
self.outbound.1.send(message).unwrap(); self.outbound.1.send(message)?;
Ok(()) Ok(())
} }
// TODO: timeout
pub fn recv(&self) -> Result<Message> { pub fn recv(&self) -> Result<Message> {
let message = self.inbound.0.recv().unwrap(); while *self.state.read() == ConnectionState::Connected {
Ok(message) match self.inbound.0.try_recv() {
Ok(message) => return Ok(message),
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => break
}
} }
fn connect(&mut self) -> Result<()> { Err(Error::ConnectionClosed)
if self.connection.is_some() {
return Ok(());
} }
pub fn connect(&mut self) -> Result<()> {
// check with a read lock first
let state = self.state.upgradable_read();
match *state {
ConnectionState::Disconnected => {
// no need to double-check after this because the read lock is upgraded instead of replace with a write lock (no possibility of mutation before the upgrade).
let mut state = RwLockUpgradableReadGuard::upgrade(state);
*state = ConnectionState::Connecting;
// we are in the connecting state and no longer need a lock to prevent the creation of many threads.
drop(state);
debug!("Connecting"); debug!("Connecting");
let mut new_connection = SocketConnection::connect()?; let mut new_connection = SocketConnection::connect()?;
@ -74,65 +80,87 @@ impl Manager {
new_connection.handshake(self.client_id)?; new_connection.handshake(self.client_id)?;
debug!("Handshake completed"); debug!("Handshake completed");
self.connection = Arc::new(Some(Mutex::new(new_connection))); let mut state = self.state.write();
match *state {
ConnectionState::Disconnected => panic!("Illegal state: {:?}", *state),
ConnectionState::Connecting => {
*state = ConnectionState::Connected;
drop(state);
debug!("Connected"); debug!("Connected");
let state_arc = self.state.clone();
let inbound = self.inbound.1.clone();
let outbound = self.outbound.0.clone();
thread::spawn(move || {
send_and_receive_loop(state_arc, inbound, outbound, new_connection);
});
Ok(()) Ok(())
} }
ConnectionState::Connected => panic!("Illegal state: {:?}", *state),
ConnectionState::Disconnecting => {
*state = ConnectionState::Disconnected;
fn disconnect(&mut self) { Err(Error::ConnectionClosed)
self.handshake_completed = false; }
self.connection = Arc::new(None); }
}
ConnectionState::Connecting => Ok(()),
ConnectionState::Connected => Ok(()),
ConnectionState::Disconnecting => Err(Error::Busy),
}
} }
pub fn disconnect(&mut self) {
let state = self.state.upgradable_read();
if *state != ConnectionState::Disconnected {
let mut state = RwLockUpgradableReadGuard::upgrade(state);
*state = ConnectionState::Disconnecting;
}
}
} }
fn send_and_receive_loop(
fn send_and_receive_loop(mut manager: Manager) { state: Arc<RwLock<ConnectionState>>,
mut inbound: Sender<Message>,
outbound: Receiver<Message>,
mut conn: SocketConnection,
) {
debug!("Starting sender loop"); debug!("Starting sender loop");
let mut inbound = manager.inbound.1.clone();
let outbound = manager.outbound.0.clone();
loop { loop {
let connection = manager.connection.clone(); match send_and_receive(&mut conn, &mut inbound, &outbound) {
Err(Error::IoError(ref err)) if err.kind() == ErrorKind::WouldBlock => {}
match *connection { Err(Error::IoError(_)) | Err(Error::ConnectionClosed) => {
Some(ref conn) => { let mut state = state.write();
let mut connection = conn.lock(); *state = ConnectionState::Disconnected;
match send_and_receive(&mut *connection, &mut inbound, &outbound) { break;
Err(Error::IoError(ref err)) if err.kind() == ErrorKind::WouldBlock => (), }
Err(Error::IoError(_)) | Err(Error::ConnectionClosed) => manager.disconnect(), Err(e) => error!("send_and_receive error: {}", e),
Err(why) => error!("error: {}", why), _ => {}
_ => (),
} }
thread::sleep(time::Duration::from_millis(500)); thread::sleep(time::Duration::from_millis(500));
},
None => {
match manager.connect() {
Err(err) => {
match err {
Error::IoError(ref err) if err.kind() == ErrorKind::ConnectionRefused => (),
why => error!("Failed to connect: {:?}", why),
}
thread::sleep(time::Duration::from_secs(10));
},
_ => manager.handshake_completed = true,
}
}
}
} }
} }
fn send_and_receive(connection: &mut SocketConnection, inbound: &mut Tx, outbound: &Rx) -> Result<()> { fn send_and_receive(
while let Ok(msg) = outbound.try_recv() { connection: &mut SocketConnection,
connection.send(msg).expect("Failed to send outgoing data"); inbound: &mut Tx,
outbound: &Rx,
) -> Result<()> {
loop {
match outbound.try_recv() {
Ok(msg) => connection.send(msg)?,
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => return Err(Error::ConnectionClosed),
}
} }
let msg = connection.recv()?; let msg = connection.recv()?;
inbound.send(msg).expect("Failed to send received data"); inbound.send(msg)?;
Ok(()) Ok(())
} }

View File

@ -5,7 +5,7 @@ mod unix;
#[cfg(windows)] #[cfg(windows)]
mod windows; mod windows;
pub use self::base::Connection as Connection; pub use self::base::Connection;
pub use self::manager::Manager; pub use self::manager::Manager;
#[cfg(unix)] #[cfg(unix)]
pub use self::unix::UnixConnection as SocketConnection; pub use self::unix::UnixConnection as SocketConnection;

View File

@ -1,15 +1,9 @@
use std::{ use std::{env, net::Shutdown, os::unix::net::UnixStream, path::PathBuf, time};
time,
path::PathBuf,
env,
os::unix::net::UnixStream,
net::Shutdown,
};
use super::base::Connection; use super::base::Connection;
use error::Result; use error::Result;
#[derive(Debug)]
pub struct UnixConnection { pub struct UnixConnection {
socket: UnixStream, socket: UnixStream,
} }
@ -27,16 +21,23 @@ impl Connection for UnixConnection {
} }
fn ipc_path() -> PathBuf { fn ipc_path() -> PathBuf {
let tmp = env::var("XDG_RUNTIME_DIR") env::var_os("XDG_RUNTIME_DIR")
.or_else(|_| env::var("TMPDIR")) .map(PathBuf::from)
.or_else(|_| { .map(|mut p| {
match env::temp_dir().to_str() { // try flatpak dir
None => Err("Failed to convert temp_dir"), p.push("app/com.discordapp.Discord");
Some(tmp) => Ok(tmp.to_string()) if !p.exists() {
p.pop();
p.pop();
} }
p
}) })
.unwrap_or("/tmp".to_string()); .or_else(|| env::var_os("TMPDIR").map(PathBuf::from))
PathBuf::from(tmp) .or_else(|| match env::temp_dir().to_str() {
None => None,
Some(tmp) => Some(PathBuf::from(tmp)),
})
.unwrap_or_else(|| PathBuf::from("/tmp"))
} }
fn socket(&mut self) -> &mut Self::Socket { fn socket(&mut self) -> &mut Self::Socket {
@ -46,7 +47,8 @@ impl Connection for UnixConnection {
impl Drop for UnixConnection { impl Drop for UnixConnection {
fn drop(&mut self) { fn drop(&mut self) {
self.socket.shutdown(Shutdown::Both) self.socket
.shutdown(Shutdown::Both)
.expect("Failed to properly shut down socket"); .expect("Failed to properly shut down socket");
} }
} }

View File

@ -1,13 +1,11 @@
use std::{ use std::{path::PathBuf, time};
time,
path::PathBuf,
};
use super::base::Connection; use super::base::Connection;
use error::Result; use error::Result;
use named_pipe::PipeClient; use named_pipe::PipeClient;
#[derive(Debug)]
pub struct WindowsConnection { pub struct WindowsConnection {
socket: PipeClient, socket: PipeClient,
} }

View File

@ -1,16 +1,14 @@
use crossbeam_channel::SendError;
use serde_json::Error as JsonError;
use std::{ use std::{
error::Error as StdError, error::Error as StdError,
fmt::{self, Display, Formatter},
io::Error as IoError, io::Error as IoError,
result::Result as StdResult, result::Result as StdResult,
sync::mpsc::RecvTimeoutError as ChannelTimeout, sync::mpsc::RecvTimeoutError as ChannelTimeout,
fmt::{
self,
Display,
Formatter
},
}; };
use serde_json::Error as JsonError;
use crate::models::Message;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -20,42 +18,48 @@ pub enum Error {
Conversion, Conversion,
SubscriptionFailed, SubscriptionFailed,
ConnectionClosed, ConnectionClosed,
ConnectionClosedWhileSending(Message),
Busy,
} }
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.description()) match self {
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::Busy => f.write_str("A resource was busy"),
Self::IoError(err) => write!(f, "{}", err),
Self::JsonError(err) => write!(f, "{}", err),
Self::Timeout(err) => write!(f, "{}", err),
}
} }
} }
impl StdError for Error { impl StdError for Error {}
fn description(&self) -> &str {
match *self {
Error::Conversion => "Failed to convert values",
Error::SubscriptionFailed => "Failed to subscribe to event",
Error::ConnectionClosed => "Connection closed",
Error::IoError(ref err) => err.description(),
Error::JsonError(ref err) => err.description(),
Error::Timeout(ref err) => err.description(),
}
}
}
impl From<IoError> for Error { impl From<IoError> for Error {
fn from(err: IoError) -> Self { fn from(err: IoError) -> Self {
Error::IoError(err) Self::IoError(err)
} }
} }
impl From<JsonError> for Error { impl From<JsonError> for Error {
fn from(err: JsonError) -> Self { fn from(err: JsonError) -> Self {
Error::JsonError(err) Self::JsonError(err)
} }
} }
impl From<ChannelTimeout> for Error { impl From<ChannelTimeout> for Error {
fn from(err: ChannelTimeout) -> Self { fn from(err: ChannelTimeout) -> Self {
Error::Timeout(err) Self::Timeout(err)
}
}
impl From<SendError<Message>> for Error {
fn from(err: SendError<Message>) -> Self {
Self::ConnectionClosedWhileSending(err.0)
} }
} }

View File

@ -6,20 +6,22 @@ extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_json; extern crate serde_json;
extern crate byteorder; extern crate byteorder;
extern crate uuid;
extern crate bytes; extern crate bytes;
extern crate parking_lot;
extern crate crossbeam_channel; extern crate crossbeam_channel;
#[cfg(windows)] #[cfg(windows)]
extern crate named_pipe; extern crate named_pipe;
extern crate parking_lot;
extern crate uuid;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod error;
mod utils;
mod connection;
pub mod models;
pub mod client; pub mod client;
mod connection;
mod error;
pub mod models;
mod utils;
pub use client::Client; pub use client::Client;
pub use connection::{Connection, SocketConnection}; pub use connection::{Connection, SocketConnection};
pub use error::*;

View File

@ -1,6 +1,5 @@
use super::shared::PartialUser; use super::shared::PartialUser;
builder! {SubscriptionArgs builder! {SubscriptionArgs
secret: String, // Activity{Join,Spectate} secret: String, // Activity{Join,Spectate}
user: PartialUser, // ActivityJoinRequest user: PartialUser, // ActivityJoinRequest

View File

@ -1,6 +1,5 @@
use super::shared::PartialUser; use super::shared::PartialUser;
builder! {ReadyEvent builder! {ReadyEvent
v: u32, v: u32,
config: RpcServerConfiguration, config: RpcServerConfiguration,

View File

@ -1,11 +1,10 @@
use std::io::{self, Write, Read}; use std::io::{self, Read, Write};
use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use serde_json;
use serde::Serialize; use serde::Serialize;
use serde_json;
use error::{Result, Error}; use error::{Error, Result};
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum OpCode { pub enum OpCode {
@ -25,7 +24,7 @@ impl OpCode {
2 => Ok(OpCode::Close), 2 => Ok(OpCode::Close),
3 => Ok(OpCode::Ping), 3 => Ok(OpCode::Ping),
4 => Ok(OpCode::Pong), 4 => Ok(OpCode::Pong),
_ => Err(Error::Conversion) _ => Err(Error::Conversion),
} }
} }
} }
@ -38,9 +37,13 @@ pub struct Message {
impl Message { impl Message {
pub fn new<T>(opcode: OpCode, payload: T) -> Self pub fn new<T>(opcode: OpCode, payload: T) -> Self
where T: Serialize where
T: Serialize,
{ {
Self { opcode, payload: serde_json::to_string(&payload).unwrap() } Self {
opcode,
payload: serde_json::to_string(&payload).unwrap(),
}
} }
pub fn encode(&self) -> Result<Vec<u8>> { pub fn encode(&self) -> Result<Vec<u8>> {
@ -65,14 +68,13 @@ impl Message {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Something { struct Something {
empty: bool empty: bool,
} }
#[test] #[test]

View File

@ -1,10 +1,9 @@
mod shared;
pub mod message;
pub mod payload;
pub mod commands; pub mod commands;
pub mod events; pub mod events;
pub mod message;
pub mod payload;
pub mod rich_presence; pub mod rich_presence;
mod shared;
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -34,30 +33,21 @@ pub enum Event {
ActivityJoinRequest, ActivityJoinRequest,
} }
pub use self::message::{Message, OpCode};
pub use self::commands::*; pub use self::commands::*;
pub use self::events::*; pub use self::events::*;
pub use self::message::{Message, OpCode};
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
pub use self::rich_presence::*; pub use self::rich_presence::*;
pub mod prelude { pub mod prelude {
pub use super::Command; pub use super::commands::{Subscription, SubscriptionArgs};
pub use super::Event; pub use super::events::{ErrorEvent, ReadyEvent};
#[cfg(feature = "rich_presence")] #[cfg(feature = "rich_presence")]
pub use super::rich_presence::{ pub use super::rich_presence::{
SetActivityArgs, ActivityJoinEvent, ActivityJoinRequestEvent, ActivitySpectateEvent,
SendActivityJoinInviteArgs, CloseActivityRequestArgs, SendActivityJoinInviteArgs, SetActivityArgs,
CloseActivityRequestArgs,
ActivityJoinEvent,
ActivitySpectateEvent,
ActivityJoinRequestEvent
};
pub use super::commands::{
SubscriptionArgs, Subscription
};
pub use super::events::{
ReadyEvent,
ErrorEvent,
}; };
pub use super::Command;
pub use super::Event;
} }

View File

@ -1,17 +1,15 @@
use std::{ use std::convert::From;
convert::From,
};
use serde::{Serialize, de::DeserializeOwned}; use serde::{de::DeserializeOwned, Serialize};
use serde_json; use serde_json;
use super::{Command, Event, Message}; use super::{Command, Event, Message};
use utils; use utils;
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Payload<T> pub struct Payload<T>
where T: Serialize where
T: Serialize,
{ {
pub cmd: Command, pub cmd: Command,
@ -29,15 +27,23 @@ pub struct Payload<T>
} }
impl<T> Payload<T> impl<T> Payload<T>
where T: Serialize where
T: Serialize,
{ {
pub fn with_nonce(cmd: Command, args: Option<T>, data: Option<T>, evt: Option<Event>) -> Self { pub fn with_nonce(cmd: Command, args: Option<T>, data: Option<T>, evt: Option<Event>) -> Self {
Self { cmd, args, data, evt, nonce: Some(utils::nonce()) } Self {
cmd,
args,
data,
evt,
nonce: Some(utils::nonce()),
}
} }
} }
impl<T> From<Message> for Payload<T> impl<T> From<Message> for Payload<T>
where T: Serialize + DeserializeOwned where
T: Serialize + DeserializeOwned,
{ {
fn from(message: Message) -> Self { fn from(message: Message) -> Self {
serde_json::from_str(&message.payload).unwrap() serde_json::from_str(&message.payload).unwrap()

View File

@ -5,7 +5,6 @@ use std::default::Default;
use super::shared::PartialUser; use super::shared::PartialUser;
use utils; use utils;
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SetActivityArgs { pub struct SetActivityArgs {
pid: u32, pid: u32,
@ -15,16 +14,20 @@ pub struct SetActivityArgs {
} }
impl SetActivityArgs { impl SetActivityArgs {
pub fn new<F>(f: F) -> Self pub fn new(activity: Activity) -> Self {
where F: FnOnce(Activity) -> Activity Self {
{ pid: utils::pid(),
Self { pid: utils::pid(), activity: Some(f(Activity::new())) } activity: Some(activity),
}
} }
} }
impl Default for SetActivityArgs { impl Default for SetActivityArgs {
fn default() -> Self { fn default() -> Self {
Self { pid: utils::pid(), activity: None } Self {
pid: utils::pid(),
activity: None,
}
} }
} }
@ -37,7 +40,9 @@ pub type CloseActivityRequestArgs = SendActivityJoinInviteArgs;
impl SendActivityJoinInviteArgs { impl SendActivityJoinInviteArgs {
pub fn new(user_id: u64) -> Self { pub fn new(user_id: u64) -> Self {
Self { user_id: user_id.to_string() } Self {
user_id: user_id.to_string(),
}
} }
} }
@ -53,7 +58,7 @@ builder!{ActivityJoinRequestEvent
user: PartialUser, user: PartialUser,
} }
// TODO: remove this stupid builder func pattern.
builder! {Activity builder! {Activity
state: String, state: String,
details: String, details: String,
@ -87,14 +92,12 @@ builder!{ActivitySecrets
game: String alias = "match", game: String alias = "match",
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use serde_json; use serde_json;
const FULL_JSON: &'static str = const FULL_JSON: &'static str = r###"{
r###"{
"state": "rusting", "state": "rusting",
"details": "detailed", "details": "detailed",
"instance": true, "instance": true,
@ -128,21 +131,19 @@ r###"{
.state("rusting") .state("rusting")
.details("detailed") .details("detailed")
.instance(true) .instance(true)
.timestamps(|t| t .timestamps(|t| t.start(1000).end(2000))
.start(1000) .assets(|a| {
.end(2000)) a.large_image("ferris")
.assets(|a| a
.large_image("ferris")
.large_text("Ferris") .large_text("Ferris")
.small_image("rusting") .small_image("rusting")
.small_text("Rusting...")) .small_text("Rusting...")
.party(|p| p })
.id(1) .party(|p| p.id(1).size((3, 6)))
.size((3, 6))) .secrets(|s| {
.secrets(|s| s s.join("025ed05c71f639de8bfaa0d679d7c94b2fdce12f")
.join("025ed05c71f639de8bfaa0d679d7c94b2fdce12f")
.spectate("e7eb30d2ee025ed05c71ea495f770b76454ee4e0") .spectate("e7eb30d2ee025ed05c71ea495f770b76454ee4e0")
.game("4b2fdce12f639de8bfa7e3591b71a0d679d7c93f")); .game("4b2fdce12f639de8bfa7e3591b71a0d679d7c93f")
});
let json = serde_json::to_string_pretty(&activity).unwrap(); let json = serde_json::to_string_pretty(&activity).unwrap();

View File

@ -1,6 +1,5 @@
use uuid::Uuid; use uuid::Uuid;
#[allow(unused)] #[allow(unused)]
pub fn pid() -> u32 { pub fn pid() -> u32 {
std::process::id() std::process::id()