use std::{
borrow::Borrow,
cmp,
collections::{BTreeSet, HashMap, HashSet},
hash::Hash,
time::Instant,
};
use anyhow::Context;
use dead_names::DeadNameMap;
use key_guard::KeyGuard;
pub(super) use key_guard::PlayerMut;
use log::{debug, trace, warn};
use zbus::{
names::{BusName, UniqueName, WellKnownName},
Connection,
};
use super::{mpris::player::PlaybackStatus, player::Action, Player};
use crate::Result;
mod dead_names;
mod key_guard;
#[derive(Debug, PartialEq, Eq)]
struct PlayerKey {
status: PlaybackStatus,
last_update: Instant,
bus: WellKnownName<'static>,
}
impl cmp::PartialOrd for PlayerKey {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
}
impl cmp::Ord for PlayerKey {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.status
.cmp(&other.status)
.then_with(|| other.last_update.cmp(&self.last_update))
.then_with(|| self.bus.cmp(&other.bus))
}
}
trait GetKey {
fn get_key(&self) -> PlayerKey;
}
impl GetKey for Player {
fn get_key(&self) -> PlayerKey {
PlayerKey {
status: self.status(),
last_update: self.last_update(),
bus: self.bus().to_owned(),
}
}
}
pub enum PlayerChange {
Add(WellKnownName<'static>),
Refresh(WellKnownName<'static>),
Remove(WellKnownName<'static>),
}
impl std::fmt::Display for PlayerChange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Add(n) => write!(f, "{n:?} added"),
Self::Refresh(n) => write!(f, "{n:?} refreshed"),
Self::Remove(n) => write!(f, "{n:?} removed"),
}
}
}
type KeyMap = BTreeSet<PlayerKey>;
fn active_keys(keys: &KeyMap) -> impl Iterator<Item = &PlayerKey> {
let mut prev = None;
keys.iter().take_while(move |p| {
let ret = prev.is_none_or(|s| s == p.status);
prev = Some(p.status);
if !ret {
debug!("Stopping active-player search before {p:?}");
}
ret
})
}
fn update_key<Q: ?Sized + Ord, P: GetKey>(keys: &mut KeyMap, old: &Q, player: &P)
where PlayerKey: Borrow<Q> {
let key = player.get_key();
if key.borrow() != old {
assert!(keys.remove(old));
assert!(keys.insert(key));
}
}
#[derive(Debug)]
pub(super) struct PlayerMap {
players: HashMap<WellKnownName<'static>, Player>,
names: HashMap<UniqueName<'static>, WellKnownName<'static>>,
dead_names: DeadNameMap,
keys: KeyGuard,
}
impl PlayerMap {
pub async fn inform<'a>(
&'a mut self,
conn: &'a Connection,
now: Instant,
try_patch: bool,
names: HashMap<UniqueName<'static>, WellKnownName<'static>>,
mut changes: Option<&mut Vec<PlayerChange>>,
) {
let curr_buses: HashSet<_> = self.players.keys().cloned().collect();
let new_buses: HashSet<_> = names.values().cloned().collect();
assert!(new_buses.len() == names.len());
self.names = names;
self.dead_names = DeadNameMap::new();
for name in curr_buses.difference(&new_buses) {
trace!("Removing player from map: {name:?}");
if let Some(ref mut c) = changes {
c.push(PlayerChange::Remove(name.clone()));
}
assert!(self.remove(name));
}
for name in new_buses.difference(&curr_buses) {
let player = match Player::new(now, BusName::WellKnown(name.into()), conn).await {
Ok(p) => p,
Err(e) => {
warn!("Error constructing player: {e:?}");
continue;
},
};
if let Some(ref mut c) = changes {
c.push(PlayerChange::Add(name.clone()));
}
assert!(self.put(player).is_none());
}
if try_patch {
for name in new_buses.intersection(&curr_buses) {
let mut player = self.get_mut(name).unwrap();
match player.refresh(now).await {
Ok(Some(_)) => {
if let Some(ref mut c) = changes {
c.push(PlayerChange::Refresh(name.clone()));
}
},
Ok(None) => (),
Err(e) => warn!("Error refreshing player: {e:?}"),
}
}
}
}
pub fn new() -> Self {
Self {
players: HashMap::new(),
names: HashMap::new(),
dead_names: DeadNameMap::new(),
keys: KeyGuard::new(),
}
}
pub fn resolve<Q: ?Sized + Hash + Eq>(&self, uniq: &Q) -> Option<&WellKnownName<'static>>
where UniqueName<'static>: Borrow<Q> {
self.names.get(uniq).or_else(|| self.dead_names.get(uniq))
}
pub fn iter_all(&self) -> impl Iterator<Item = &Player> {
self.keys.iter().map(|k| &self.players[&k.bus])
}
pub fn iter_active(&self) -> impl Iterator<Item = &Player> {
active_keys(&self.keys).map(|k| &self.players[&k.bus])
}
pub fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, bus: &Q) -> bool
where WellKnownName<'static>: Borrow<Q> {
self.players.contains_key(bus)
}
pub fn get_mut<Q: ?Sized + Hash + Eq>(&mut self, bus: &Q) -> Option<PlayerMut>
where WellKnownName<'static>: Borrow<Q> {
self.players
.get_mut(bus)
.map(|player| PlayerMut::new(player, &mut self.keys))
}
pub async fn apply_action<A: Action>(&mut self, action: A) -> Result<Option<A::Output>> {
let mut keys = active_keys(&self.keys);
let found = loop {
let Some(key) = keys.next() else { break None };
let player = self.players.get_mut(&key.bus).unwrap();
if let Some(arg) = action.can_run(player).await.unwrap_or_else(|e| {
warn!("Error querying player {:?} for action: {e:?}", key.bus);
None
}) {
break Some((player, arg));
}
};
let Some((player, arg)) = found else {
return Ok(None);
};
drop(keys);
let mut player = PlayerMut::new(player, &mut self.keys);
action.run(&mut player, arg).await.map(Some)
}
pub fn put(&mut self, player: Player) -> Option<Player> {
use std::collections::hash_map::Entry;
match self.players.entry(player.bus().to_owned()) {
Entry::Vacant(v) => {
trace!("Inserting new player into map: {:?}", player.bus());
assert!(self.keys.insert(player.get_key()));
v.insert(player);
None
},
Entry::Occupied(o) => {
trace!("Patching existing player in map: {:?}", player.bus());
let value = o.into_mut();
update_key(&mut self.keys, &value.get_key(), &player);
Some(std::mem::replace(value, player))
},
}
}
pub fn remove<Q: ?Sized + Hash + Eq + std::fmt::Debug>(&mut self, bus: &Q) -> bool
where WellKnownName<'static>: Borrow<Q> {
if let Some(old_player) = self.players.remove(bus) {
trace!("Removing player from map: {bus:?}");
assert!(self.keys.remove(&old_player.get_key()));
true
} else {
warn!("Player to remove does not exist in map: {bus:?}");
false
}
}
pub async fn put_owner(
&mut self,
conn: &Connection,
now: Instant,
uniq: UniqueName<'static>,
bus: WellKnownName<'static>,
) -> Result<(Option<WellKnownName<'static>>, bool)> {
#[inline]
async fn insert_new(
this: &mut PlayerMap,
conn: &Connection,
now: Instant,
bus: WellKnownName<'static>,
) -> Result<()> {
if !this.players.contains_key(&bus) {
assert!(this
.put(
Player::new(now, bus, conn)
.await
.context("Error constructing a new player")?,
)
.is_none());
}
Ok(())
}
if let Some(old_bus) = self.names.insert(uniq, bus.clone()) {
if old_bus == bus {
debug_assert!(self.players.contains_key(&bus));
Ok((Some(old_bus), false))
} else {
warn!("Replacing bus name {old_bus:?} with {bus:?}");
let ret = self.remove(&old_bus);
insert_new(self, conn, now, bus).await?;
Ok((Some(old_bus), ret))
}
} else {
insert_new(self, conn, now, bus).await?;
Ok((None, false))
}
}
pub fn remove_owner<Q: ?Sized + Hash + Eq + ToOwned<Owned = UniqueName<'static>>>(
&mut self,
uniq: &Q,
) -> Option<(WellKnownName<'static>, bool)>
where
UniqueName<'static>: Borrow<Q>,
{
let bus = self.names.remove(uniq)?;
let ret = self.remove(&bus);
self.dead_names.insert(uniq.to_owned(), bus.clone());
Some((bus, ret))
}
}