use thiserror::Error;
use std::iter::FusedIterator;
use std::rc::Rc;
use dbus::ffidisp::{BusType, Connection};
use dbus::{arg, Message};
use super::DBusError;
use crate::player::{Player, DEFAULT_TIMEOUT_MS, MPRIS2_PREFIX};
use crate::pooled_connection::PooledConnection;
use crate::PlaybackStatus;
const LIST_NAMES_TIMEOUT_MS: i32 = 500;
#[derive(Debug, Error)]
pub enum FindingError {
#[error("No player found")]
NoPlayerFound,
#[error("{0}")]
DBusError(#[from] DBusError),
}
impl From<dbus::Error> for FindingError {
fn from(error: dbus::Error) -> Self {
FindingError::DBusError(error.into())
}
}
#[derive(Debug)]
pub struct PlayerFinder {
connection: Rc<PooledConnection>,
player_timeout_ms: i32,
}
impl PlayerFinder {
pub fn new() -> Result<Self, DBusError> {
Ok(PlayerFinder::for_connection(Connection::get_private(
BusType::Session,
)?))
}
pub fn for_connection(connection: Connection) -> Self {
PlayerFinder {
connection: Rc::new(connection.into()),
player_timeout_ms: DEFAULT_TIMEOUT_MS,
}
}
pub fn player_timeout_ms(&self) -> i32 {
self.player_timeout_ms
}
pub fn set_player_timeout_ms(&mut self, timeout_ms: i32) {
self.player_timeout_ms = timeout_ms;
}
pub fn find_all(&self) -> Result<Vec<Player>, FindingError> {
self.iter_players()?
.map(|x| x.map_err(FindingError::from))
.collect()
}
pub fn find_first(&self) -> Result<Player, FindingError> {
if let Some(player) = self.iter_players()?.next() {
player.map_err(FindingError::from)
} else {
Err(FindingError::NoPlayerFound)
}
}
pub fn find_active(&self) -> Result<Player, FindingError> {
let players: PlayerIter = self.iter_players()?;
match self.find_active_player(players)? {
Some(player) => Ok(player),
None => Err(FindingError::NoPlayerFound),
}
}
fn find_active_player(&self, players: PlayerIter) -> Result<Option<Player>, DBusError> {
if players.len() == 0 {
return Ok(None);
}
let mut first_paused: Option<Player> = None;
let mut first_with_track: Option<Player> = None;
let mut first_found: Option<Player> = None;
for player in players {
let player = player?;
let player_status = player.get_playback_status()?;
if player_status == PlaybackStatus::Playing {
return Ok(Some(player));
}
if first_paused.is_none() && player_status == PlaybackStatus::Paused {
first_paused.replace(player);
} else if first_with_track.is_none() && !player.get_metadata()?.is_empty() {
first_with_track.replace(player);
} else if first_found.is_none() {
first_found.replace(player);
}
}
Ok(first_paused.or(first_with_track).or(first_found))
}
pub fn find_by_name(&self, name: &str) -> Result<Player, FindingError> {
for player_result in self.iter_players()? {
let player = player_result?;
if player.identity().to_lowercase() == name.to_lowercase() {
return Ok(player);
}
}
Err(FindingError::NoPlayerFound)
}
fn all_player_buses(&self) -> Result<Vec<String>, DBusError> {
let list_names = Message::new_method_call(
"org.freedesktop.DBus",
"/",
"org.freedesktop.DBus",
"ListNames",
)
.unwrap();
let reply = self
.connection
.underlying()
.send_with_reply_and_block(list_names, LIST_NAMES_TIMEOUT_MS)?;
let names: arg::Array<'_, &str, _> = reply.read1().map_err(DBusError::from)?;
let mut all_busses = names
.filter(|name| name.starts_with(MPRIS2_PREFIX))
.map(|str_ref| str_ref.to_owned())
.collect::<Vec<String>>();
all_busses.sort_by_key(|a| a.to_lowercase());
Ok(all_busses)
}
pub fn iter_players(&self) -> Result<PlayerIter, DBusError> {
let buses = self.all_player_buses()?;
Ok(PlayerIter::new(
buses,
self.connection.clone(),
self.player_timeout_ms,
))
}
}
#[derive(Debug)]
pub struct PlayerIter {
buses: std::vec::IntoIter<String>,
connection: Rc<PooledConnection>,
timeout_ms: i32,
}
impl PlayerIter {
fn new(buses: Vec<String>, connection: Rc<PooledConnection>, timeout_ms: i32) -> Self {
Self {
buses: buses.into_iter(),
connection,
timeout_ms,
}
}
}
impl Iterator for PlayerIter {
type Item = Result<Player, DBusError>;
fn next(&mut self) -> Option<Self::Item> {
let bus = self.buses.next()?;
Some(Player::for_pooled_connection(
self.connection.clone(),
bus,
self.timeout_ms,
))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.buses.len();
(size, Some(size))
}
}
impl ExactSizeIterator for PlayerIter {}
impl FusedIterator for PlayerIter {}