use super::{DBusError, Metadata, Player};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::iter::{FromIterator, IntoIterator};
use thiserror::Error;
pub(crate) const NO_TRACK: &str = "/org/mpris/MediaPlayer2/TrackList/NoTrack";
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub struct TrackID(pub(crate) String);
#[derive(Debug, Default)]
pub struct TrackList {
ids: Vec<TrackID>,
metadata_cache: RefCell<HashMap<TrackID, Metadata>>,
}
#[derive(Debug, Error)]
pub enum TrackListError {
#[error("D-Bus communication failed: {0}")]
DBusError(#[from] DBusError),
#[error("Could not borrow cache: {0}")]
BorrowError(String),
}
#[derive(Debug)]
pub struct MetadataIter {
order: Vec<TrackID>,
metadata: HashMap<TrackID, Metadata>,
current: usize,
}
impl<'a> From<dbus::Path<'a>> for TrackID {
fn from(path: dbus::Path<'a>) -> TrackID {
TrackID(path.to_string())
}
}
impl<'a> From<&'a TrackID> for TrackID {
fn from(id: &'a TrackID) -> Self {
TrackID(id.0.clone())
}
}
impl From<TrackID> for String {
fn from(id: TrackID) -> String {
id.0
}
}
impl<'a> From<&'a TrackID> for dbus::Path<'a> {
fn from(id: &'a TrackID) -> dbus::Path<'a> {
id.as_path()
}
}
impl fmt::Display for TrackID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl TrackID {
pub fn new<S: Into<String>>(id: S) -> Result<Self, String> {
let id = id.into();
if let Err(error) = dbus::Path::new(id.as_str()) {
Err(error)
} else {
Ok(TrackID(id))
}
}
pub fn no_track() -> Self {
TrackID(NO_TRACK.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub(crate) fn as_path(&self) -> dbus::Path<'_> {
dbus::Path::new(self.as_str()).unwrap()
}
}
impl AsRef<str> for TrackID {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<Vec<TrackID>> for TrackList {
fn from(ids: Vec<TrackID>) -> Self {
TrackList::new(ids)
}
}
impl<'a> From<Vec<dbus::Path<'a>>> for TrackList {
fn from(ids: Vec<dbus::Path<'a>>) -> Self {
ids.into_iter().map(TrackID::from).collect()
}
}
impl FromIterator<TrackID> for TrackList {
fn from_iter<I: IntoIterator<Item = TrackID>>(iter: I) -> Self {
TrackList::new(iter.into_iter().collect())
}
}
impl TrackList {
pub fn new(ids: Vec<TrackID>) -> TrackList {
TrackList {
metadata_cache: RefCell::new(HashMap::with_capacity(ids.len())),
ids,
}
}
pub fn ids(&self) -> &[TrackID] {
self.ids.as_ref()
}
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn get(&self, index: usize) -> Option<&TrackID> {
self.ids.get(index)
}
pub fn insert(&mut self, after: &TrackID, metadata: Metadata) {
let new_id = match metadata.track_id() {
Some(val) => val,
None => return,
};
let index = self.index_of_id(after).unwrap_or(self.ids.len());
if index >= self.ids.len() {
self.ids.push(new_id.clone());
} else {
self.ids.insert(index + 1, new_id.clone());
}
self.change_metadata(|cache| cache.insert(new_id, metadata));
}
pub fn remove(&mut self, id: &TrackID) {
self.ids.retain(|existing_id| existing_id != id);
self.change_metadata(|cache| cache.remove(id));
}
pub fn clear(&mut self) {
self.ids.clear();
self.change_metadata(|cache| cache.clear());
}
pub fn replace(&mut self, other: TrackList) {
self.ids = other.ids;
let other_cache = other.metadata_cache.into_inner();
self.change_metadata(|self_cache| {
self_cache.extend(other_cache);
});
}
pub fn add_metadata(&mut self, metadata: Metadata) {
if let Some(id) = metadata.track_id() {
self.change_metadata(|cache| cache.insert(id.to_owned(), metadata));
}
}
pub fn replace_track_metadata(
&mut self,
old_id: &TrackID,
new_metadata: Metadata,
) -> Option<TrackID> {
if let Some(new_id) = new_metadata.track_id() {
if let Some(index) = self.index_of_id(old_id) {
self.ids[index] = new_id.to_owned();
self.change_metadata(|cache| cache.insert(new_id.to_owned(), new_metadata));
return Some(new_id);
}
}
None
}
pub fn metadata_iter(&self, player: &Player) -> Result<MetadataIter, TrackListError> {
self.complete_cache(player)?;
let metadata: HashMap<_, _> = self.metadata_cache.clone().into_inner();
let ids = self.ids.clone();
Ok(MetadataIter {
current: 0,
order: ids,
metadata,
})
}
pub fn reload(&mut self, player: &Player) -> Result<(), TrackListError> {
self.ids = player.get_track_list()?.ids;
self.clear_extra_cache();
Ok(())
}
pub fn reload_cache(&self, player: &Player) -> Result<(), TrackListError> {
let id_metadata = self
.ids
.iter()
.cloned()
.zip(player.get_tracks_metadata(&self.ids)?);
let mut cache = self.metadata_cache.try_borrow_mut()?;
*cache = id_metadata.collect();
Ok(())
}
pub fn complete_cache(&self, player: &Player) -> Result<(), TrackListError> {
let ids: Vec<_> = self.ids_without_cache().into_iter().cloned().collect();
if !ids.is_empty() {
let metadata = player.get_tracks_metadata(&ids)?;
let mut cache = self.metadata_cache.try_borrow_mut()?;
for info in metadata.into_iter() {
if let Some(id) = info.track_id() {
cache.insert(id, info);
}
}
}
Ok(())
}
fn change_metadata<T, F>(&mut self, f: F) -> T
where
F: FnOnce(&mut HashMap<TrackID, Metadata>) -> T,
{
let mut cache = self.metadata_cache.borrow_mut(); f(&mut cache)
}
fn ids_without_cache(&self) -> Vec<&TrackID> {
let cache = &*self.metadata_cache.borrow();
self.ids
.iter()
.filter(|id| !cache.contains_key(id))
.collect()
}
fn clear_extra_cache(&mut self) {
let ids: Vec<TrackID> = self.ids().iter().map(TrackID::from).collect();
self.change_metadata(|cache| {
let new_cache: HashMap<TrackID, Metadata> = ids
.iter()
.flat_map(|id| cache.remove(id).map(|value| (id.to_owned(), value)))
.collect();
*cache = new_cache;
});
}
fn index_of_id(&self, id: &TrackID) -> Option<usize> {
self.ids
.iter()
.enumerate()
.find(|(_, item_id)| *item_id == id)
.map(|(index, _)| index)
}
}
impl PartialEq<TrackList> for TrackList {
fn eq(&self, other: &TrackList) -> bool {
self.ids.eq(&other.ids)
}
}
impl Iterator for MetadataIter {
type Item = Metadata;
fn next(&mut self) -> Option<Self::Item> {
match self.order.get(self.current) {
Some(next_id) => {
self.current += 1;
Some(
self.metadata
.remove(next_id)
.unwrap_or_else(|| Metadata::new(next_id.clone())),
)
}
None => None,
}
}
}
impl From<::std::cell::BorrowMutError> for TrackListError {
fn from(error: ::std::cell::BorrowMutError) -> TrackListError {
TrackListError::BorrowError(format!("Could not borrow mutably: {}", error))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn track_id(s: &str) -> TrackID {
TrackID::new(s).expect("Failed to parse a TrackID fixture")
}
mod track_list {
use super::*;
#[test]
fn it_inserts_after_given_id() {
let first = track_id("/path/1");
let third = track_id("/path/3");
let mut list = TrackList {
ids: vec![first, third],
metadata_cache: RefCell::new(HashMap::new()),
};
let metadata = Metadata::new("/path/new");
list.insert(&track_id("/path/1"), metadata);
assert_eq!(list.len(), 3);
assert_eq!(
&list.ids,
&[
track_id("/path/1"),
track_id("/path/new"),
track_id("/path/3")
]
);
assert_eq!(
list.ids_without_cache(),
vec![&track_id("/path/1"), &track_id("/path/3")],
);
}
#[test]
fn it_inserts_at_end_on_missing_id() {
let first = track_id("/path/1");
let third = track_id("/path/3");
let mut list = TrackList {
ids: vec![first, third],
metadata_cache: RefCell::new(HashMap::new()),
};
let metadata = Metadata::new("/path/new");
list.insert(&track_id("/path/missing"), metadata);
assert_eq!(list.len(), 3);
assert_eq!(
&list.ids,
&[
track_id("/path/1"),
track_id("/path/3"),
track_id("/path/new"),
]
);
assert_eq!(
list.ids_without_cache(),
vec![&track_id("/path/1"), &track_id("/path/3")],
);
}
#[test]
fn it_inserts_at_end_on_empty() {
let mut list = TrackList::default();
let metadata = Metadata::new("/path/new");
list.insert(&track_id("/path/missing"), metadata);
assert_eq!(list.len(), 1);
assert_eq!(&list.ids, &[track_id("/path/new")]);
assert!(list.ids_without_cache().is_empty());
}
}
}