Skip to content

Commit

Permalink
Rewrote method handling and added a seek command.
Browse files Browse the repository at this point in the history
  • Loading branch information
ray-kast committed Mar 13, 2021
1 parent 9d2cca5 commit 99a16db
Show file tree
Hide file tree
Showing 8 changed files with 567 additions and 189 deletions.
25 changes: 8 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "empress"
version = "1.1.3"
version = "1.2.0"
authors = ["rookie1024 <[email protected]>"]
edition = "2018"
description = "A D-Bus MPRIS daemon for controlling media players."
Expand All @@ -21,5 +21,6 @@ env_logger = "0.8.3"
futures = "0.3.13"
lazy_static = "1.4.0"
log = "0.4.14"
regex = "1.4.4"
structopt = "0.3.21"
tokio = { version = "1.2.0", features = ["macros", "rt", "signal", "sync"] }
53 changes: 34 additions & 19 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ use std::{sync::Arc, time::Duration};

use anyhow::{Context, Error};
use dbus::{
arg::ReadAll,
arg::{AppendAll, ReadAll},
nonblock::{Proxy, SyncConnection},
strings::Member,
MethodErr,
};
use dbus_tokio::connection;
use log::{info, warn};
use tokio::{select, sync::oneshot, task};

use crate::{ClientCommand, ExtraMethodId, Result, INTERFACE_NAME, SERVER_NAME, SERVER_PATH};
use crate::{
ClientCommand, MethodId, PlayerOpts, Result, INTERFACE_NAME, SERVER_NAME, SERVER_PATH,
};

pub(super) async fn run(cmd: ClientCommand) -> Result {
let (res, conn) = connection::new_session_sync().context("failed to connect to D-Bus")?;
Expand All @@ -26,19 +26,30 @@ pub(super) async fn run(cmd: ClientCommand) -> Result {
let run = async move {
let proxy = Proxy::new(&*SERVER_NAME, &*SERVER_PATH, Duration::from_secs(2), conn);

let id = cmd.id();

match cmd {
ClientCommand::Extra(e) => match e {
ExtraMethodId::ListPlayers => {
let (players,): (Vec<(String, String)>,) =
try_send(&proxy, e.to_string()).await?;
ClientCommand::Next(opts)
| ClientCommand::Previous(opts)
| ClientCommand::Pause(opts)
| ClientCommand::PlayPause(opts)
| ClientCommand::Stop(opts)
| ClientCommand::Play(opts) => {
let PlayerOpts {} = opts;
try_send(&proxy, id, ()).await?;
},
ClientCommand::ListPlayers => {
let (players,): (Vec<(String, String)>,) = try_send(&proxy, id, ()).await?;

for (player, status) in players {
println!("{}\t{}", player, status);
}
},
for (player, status) in players {
println!("{}\t{}", player, status);
}
},
ClientCommand::Control(c) => {
try_send(&proxy, c.to_string()).await?;
ClientCommand::Seek {
player: PlayerOpts {},
to,
} => {
try_send(&proxy, id, (to.pos(),)).await?;
},
}

Expand All @@ -53,17 +64,21 @@ pub(super) async fn run(cmd: ClientCommand) -> Result {
)
}

async fn try_send<T: ReadAll + 'static, M: for<'a> Into<Member<'a>>>(
async fn try_send<R: ReadAll + 'static, A: AppendAll + Clone>(
proxy: &Proxy<'_, Arc<SyncConnection>>,
method: M,
) -> Result<T> {
method: MethodId,
args: A,
) -> Result<R> {
const MAX_TRIES: usize = 5;

let method = method.into();
let method = method.to_string();
let mut i = 0;

loop {
match proxy.method_call(&*INTERFACE_NAME, &method, ()).await {
match proxy
.method_call(&*INTERFACE_NAME, &method, args.clone())
.await
{
Err(e) if i < MAX_TRIES => warn!("Request failed: {}", e),
r => break r.context("failed to contact empress server"),
}
Expand Down
131 changes: 101 additions & 30 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(missing_docs, clippy::all, clippy::pedantic, clippy::cargo)]
#![deny(broken_intra_doc_links, missing_debug_implementations)]
#![deny(missing_debug_implementations)]

//! Binary crate for `empress`. See [the
//! README](https://github.com/ray-kast/empress/blob/master/README.md) for more
Expand All @@ -10,7 +10,7 @@ use std::{
fmt::{Display, Formatter},
};

use anyhow::{Context, Error};
use anyhow::{anyhow, Context, Error};
use lazy_static::lazy_static;
use log::{error, LevelFilter};
use structopt::StructOpt;
Expand Down Expand Up @@ -40,14 +40,10 @@ lazy_static! {
static ref SERVER_PATH: String = format!("{}/Daemon", *PATH_PREFIX);
}

#[derive(Debug, Clone, Copy, StructOpt)]
enum ExtraMethodId {
#[derive(Debug, Clone, Copy)]
enum MethodId {
/// List the players currently tracked by the daemon
ListPlayers,
}

#[derive(Debug, Clone, Copy, StructOpt)]
enum ControlMethodId {
/// Skip one track forwards
Next,
/// Skip one track backwards
Expand All @@ -60,34 +56,24 @@ enum ControlMethodId {
Stop,
/// Play a currently-paused player
Play,
/// Seek to a relative position on a player
SeekRelative,
/// Seek to an absolute position on a player
SeekAbsolute,
}

const CONTROL_METHOD_IDS: &[ControlMethodId] = &[
ControlMethodId::Next,
ControlMethodId::Previous,
ControlMethodId::Pause,
ControlMethodId::PlayPause,
ControlMethodId::Stop,
ControlMethodId::Play,
];

impl Display for ExtraMethodId {
impl Display for MethodId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self {
Self::ListPlayers => "ListPlayers",
})
}
}

impl Display for ControlMethodId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self {
Self::Next => "Next",
Self::Previous => "Previous",
Self::Pause => "Pause",
Self::PlayPause => "PlayPause",
Self::Stop => "Stop",
Self::Play => "Play",
Self::SeekRelative => "SeekRelative",
Self::SeekAbsolute => "SeekAbsolute",
})
}
}
Expand Down Expand Up @@ -118,12 +104,97 @@ enum Opts {
Client(ClientCommand),
}

#[derive(StructOpt)]
#[derive(Debug, Clone, StructOpt)]
enum ClientCommand {
#[structopt(flatten)]
Extra(ExtraMethodId),
#[structopt(flatten)]
Control(ControlMethodId),
/// List the players currently tracked by the daemon
ListPlayers,
/// Skip one track forwards
Next(PlayerOpts),
/// Skip one track backwards
Previous(PlayerOpts),
/// Pause a currently-playing player
Pause(PlayerOpts),
/// Like pause if a player is playing, otherwise like play
PlayPause(PlayerOpts),
/// Stop a currently-playing player
Stop(PlayerOpts),
/// Play a currently-paused player
Play(PlayerOpts),
/// Seek to a position on a player
Seek {
#[structopt(flatten)]
player: PlayerOpts,

/// The position to seek to, either absolute (e.g. 5) or relative (e.g.
/// +5 or -5)
to: SeekPosition,
},
}

impl ClientCommand {
pub fn id(&self) -> MethodId {
match self {
Self::ListPlayers => MethodId::ListPlayers,
Self::Next(..) => MethodId::Next,
Self::Previous(..) => MethodId::Previous,
Self::Pause(..) => MethodId::Pause,
Self::PlayPause(..) => MethodId::PlayPause,
Self::Stop(..) => MethodId::Stop,
Self::Play(..) => MethodId::Play,
Self::Seek {
to: SeekPosition::Relative(..),
..
} => MethodId::SeekRelative,
Self::Seek {
to: SeekPosition::Absolute(..),
..
} => MethodId::SeekAbsolute,
}
}
}

#[derive(Debug, Clone, StructOpt)]
struct PlayerOpts {} // WARNING: DO NOT TOUCH WITHOUT INCREMENTING MAJOR VERSION

#[derive(Debug, Clone, Copy)]
enum SeekPosition {
Relative(f64),
Absolute(f64),
}

impl SeekPosition {
fn pos(&self) -> f64 {
*match self {
Self::Relative(p) | Self::Absolute(p) => p,
}
}
}

impl ::std::str::FromStr for SeekPosition {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
use regex::Regex;

lazy_static! {
static ref SEEK_PATTERN: Regex = Regex::new(r"(\+|-)?(\d+(?:\.\d+)?)").unwrap();
}

if let Some(caps) = SEEK_PATTERN.captures(s) {
let time: f64 = caps[2]
.parse()
.context("invalid number for seek position")?;

Ok(match caps.get(1).map(|c| c.as_str()) {
Some("-") => SeekPosition::Relative(-time),
Some("+") => SeekPosition::Relative(time),
Some(_) => unreachable!(),
None => SeekPosition::Absolute(time),
})
} else {
Err(anyhow!("invalid format for seek position"))
}
}
}

fn log_cfg(v: usize) -> env_logger::Builder {
Expand Down
Loading

0 comments on commit 99a16db

Please sign in to comment.