From d34e842f3ee3632768bb87ad77852629efa1d40f Mon Sep 17 00:00:00 2001 From: djairoh Date: Mon, 22 Apr 2024 17:18:48 +0200 Subject: [PATCH] fix: workaround for a bug where playing players are somtimes incorrectly reported as paused --- src/update_message.rs | 156 +++++++++++++++++++++++++----------------- src/update_players.rs | 77 ++++++++++++--------- 2 files changed, 136 insertions(+), 97 deletions(-) diff --git a/src/update_message.rs b/src/update_message.rs index 510ea97..3306d50 100644 --- a/src/update_message.rs +++ b/src/update_message.rs @@ -1,4 +1,4 @@ -//! This file deals with updating the actual message, including proper formatting. +//! This file deals with updating the actual message, including proper formatting. use log::{debug, trace}; use mpris::MetadataValue; @@ -9,96 +9,126 @@ use crate::structs::{config::Config, data::Data}; /// Both the Map and Unsupported types currently lead the program to panic! /// The HashMap because I honestly don't know when a metadata value would be encoded as such (and am too lazy to dig through the crate's source code), /// The Unsupported type should be self-explanatory. -/// +/// /// Input: /// v: MetadataValue to convert. /// sep: seperation character to insert between entries of a Vec. -/// +/// /// Output: /// String representing the input MetadataValue. fn value_to_string(v: &MetadataValue, sep: char) -> String { - match v { - MetadataValue::String(v) => v.to_string(), - MetadataValue::I16(v) => v.to_string(), - MetadataValue::I32(v) => v.to_string(), - MetadataValue::I64(v) => v.to_string(), - MetadataValue::U8(v) => v.to_string(), - MetadataValue::U16(v) => v.to_string(), - MetadataValue::U32(v) => v.to_string(), - MetadataValue::U64(v) => v.to_string(), - MetadataValue::F64(v) => v.to_string(), - MetadataValue::Bool(v) => v.to_string(), - MetadataValue::Array(v) => { - let mut out = v.iter().map( |val| { - let mut str = value_to_string(val, sep); - str.push(sep); - str - }).collect::(); - out.pop(); - out - }, - MetadataValue::Map(_v) => panic!("unimplemented! TBH i have no clue when a metadataValue would even return this?"), - MetadataValue::Unsupported => panic!("Unsupported Metadata type detected!"), - } + match v { + MetadataValue::String(v) => v.to_string(), + MetadataValue::I16(v) => v.to_string(), + MetadataValue::I32(v) => v.to_string(), + MetadataValue::I64(v) => v.to_string(), + MetadataValue::U8(v) => v.to_string(), + MetadataValue::U16(v) => v.to_string(), + MetadataValue::U32(v) => v.to_string(), + MetadataValue::U64(v) => v.to_string(), + MetadataValue::F64(v) => v.to_string(), + MetadataValue::Bool(v) => v.to_string(), + MetadataValue::Array(v) => { + let mut out = v + .iter() + .map(|val| { + let mut str = value_to_string(val, sep); + str.push(sep); + str + }) + .collect::(); + out.pop(); + out + } + MetadataValue::Map(_v) => { + panic!("unimplemented! TBH i have no clue when a metadataValue would even return this?") + } + MetadataValue::Unsupported => panic!("Unsupported Metadata type detected!"), + } } /// This function converts one specific instance of MetadataValue to an appropriate String. /// It deals with the xesam:userRating type. This is a float (0.0 <= v <= 1.0), but should be represented on a scale fron 0 to 10 (according to me). /// As such, it converts the float value to a visually appealing 5-symbol string. -/// +/// /// Input: /// r: MetadataValue, should be of the enum type f64 (unchecked). /// str: Vec containing precomputed rating strings to select from. -/// +/// /// Output: /// Some(String) if a rating exists, None otherwise. fn rating_to_string(r: Option<&MetadataValue>, str: &Vec) -> Option { - match r { - Some(rating) => { - if let Some(f) = rating.as_f64() { - let mut i = (f * 10_f64).round() as i64; - if i > 10 {i = 10} - if i < 0 {i = 0} + match r { + Some(rating) => { + if let Some(f) = rating.as_f64() { + let mut i = (f * 10_f64).round() as i64; + if i > 10 { + i = 10 + } + if i < 0 { + i = 0 + } - Some(str[i as usize].to_owned()) //TODO: still inefficient. would be better to note the idx and load it in print_text - } else { - debug!("failed to convert MetadataValue to f64!"); - None - } - }, - None => { - trace!("no userRating MetadataValue found!"); - None - }, - } + Some(str[i as usize].to_owned()) //TODO: still inefficient. would be better to note the idx and load it in print_text + } else { + debug!("failed to convert MetadataValue to f64!"); + None + } + } + None => { + trace!("no userRating MetadataValue found!"); + None + } + } } /// This higher level function updates the to be output Hashmap of strings. /// It does so by querying each metadata field in config to the current player, then updating the Hashmap in Data with the new value(s). /// "xesam:userRating" is treated separately, due to requiring a different output format. -/// +/// /// Input: /// cfg: Config struct for the program. Contains the wanted metadata fields. /// data: mutable Data struct for the program. Its' Hashmap containing strings is updated. /// ratings: Vec of precomputed rating strings. pub fn update_message(cfg: &Config, data: &mut Data, ratings: &Vec) { - if let Some(player) = &data.current_player { - if let Ok(meta) = player.get_metadata() { - for field in &cfg.metadata_fields { - let key: &str = field.field.as_ref(); - if field.field.eq("xesam:userRating") { - if let Some(rating_string) = rating_to_string(meta.get(key), ratings) { - data.field_text.insert(key.to_owned(), rating_string); - } else { - data.field_text.remove(key); - } + if let Some(player) = &data.current_player { + if let Ok(meta) = player.get_metadata() { + for field in &cfg.metadata_fields { + let key: &str = field.field.as_ref(); + if field.field.eq("xesam:userRating") { + if let Some(rating_string) = rating_to_string(meta.get(key), ratings) { + data.field_text.insert(key.to_owned(), rating_string); + } else { + data.field_text.remove(key); + } + } else { + match meta.get(&key) { + Some(value) => { + trace!( + "update_messages: field {} has value {}", + key, + value_to_string(value, cfg.array_separator) + ); + data.field_text + .insert(key.to_owned(), value_to_string(value, cfg.array_separator)) + } + None => { + trace!("update_messages: field {} has no value!", key); + data.field_text.insert( + key.to_owned(), + format!("No {}", key.trim_start_matches("xesam:")), + ) + } + }; + } + } } else { - match meta.get(&key) { - Some(value) => data.field_text.insert(key.to_owned(), value_to_string(value, cfg.array_separator)), - None => data.field_text.insert(key.to_owned(), format!("No {}", key.trim_start_matches("xesam:"))), - }; + debug!( + "update_messages: Player {} has no metadata!", + player.unique_name() + ); } - } + } else { + debug!("update_messages: No player found!"); } - } -} \ No newline at end of file +} diff --git a/src/update_players.rs b/src/update_players.rs index ec95617..37c1b4a 100644 --- a/src/update_players.rs +++ b/src/update_players.rs @@ -1,57 +1,66 @@ -//! This file deals with updating the active player. +//! This file deals with updating the active player. //! It also updates the prefix, which kind of breaks seperation of concerns, but this saves me a lot of headache so I'm not changing it. use std::collections::BTreeMap; -use log::trace; -use mpris::{PlayerFinder, Player}; -use crate::structs::{data::Data, config::Config}; - +use crate::structs::{config::Config, data::Data}; +use log::{debug, trace}; +use mpris::PlayerFinder; /// This function updates the current prefix. /// If no entry is found in config containing the active player, a default value is used instead ('>'). -/// +/// /// Input: /// cfg: Config struct for the program, containing the hashmap of prefixes. /// data: mutable char containing the active prefix. /// name: name of active player, to fetch the appropriate prefix from cfg. fn update_prefix(cfg: &Config, data: &mut char, name: &str) { - if let Some(char) = cfg.player_prefixes.get(name) { - *data = *char; - trace!("updated prefix to {}", data); - } else { - *data = *cfg.player_prefixes.get("default").unwrap_or(&'>'); - trace!("set prefix to default ({})", data); - } + if let Some(char) = cfg.player_prefixes.get(name) { + *data = *char; + trace!("updated prefix to {}", data); + } else { + *data = *cfg.player_prefixes.get("default").unwrap_or(&'>'); + trace!("set prefix to default ({})", data); + } } /// This function updates which player is selected as 'active'. /// It only considers players present in the config.player_priorities field to be valid candidates, then selects the active one with the highest rating. /// If none of the acceptable players are available, current_player is set to None instead. -/// +/// /// Input: /// pf: PlayerFinder instance of the program. /// cfg: Config struct of the program, containing the list of acceptable players. /// data: mutable Data struct of the program, containing a marker for the currently active player. pub fn update_players(pf: &PlayerFinder, cfg: &Config, data: &mut Data) { - // get all acceptable players - let players = pf.find_all().unwrap_or(Vec::new()); - if players.is_empty() { - data.current_player = None; - } else { - let mut active: BTreeMap = BTreeMap::new(); - for player in players { - if let Ok(mpris::PlaybackStatus::Playing) = player.get_playback_status() { - let idx = cfg.find_player_priorities_idx(player.identity()); - active.insert(idx, player); - } - } - - // select the player with the highest priority. - if let Some((_, player)) = active.pop_first() { - update_prefix(cfg, &mut data.prefix, player.identity()); - data.current_player = Some(player); + // get all acceptable players + let players = pf.find_all().unwrap_or(Vec::new()); + if players.is_empty() { + data.current_player = None; + debug!("update_players: no players found!") } else { - data.current_player = None; + let mut trees = vec![BTreeMap::new(), BTreeMap::new(), BTreeMap::new()]; + for player in players { + if let Ok(status) = player.get_playback_status() { + let idx = cfg.find_player_priorities_idx(player.identity()); + match status { + mpris::PlaybackStatus::Playing => trees[0].insert(idx, player), + mpris::PlaybackStatus::Paused => trees[1].insert(idx, player), + mpris::PlaybackStatus::Stopped => trees[2].insert(idx, player), + }; + } + } + + // select the player with the highest priority. + for mut tree in trees { + if let Some((_, player)) = tree.pop_first() { + update_prefix(cfg, &mut data.prefix, player.identity()); + debug!("update_players: updated player to {}!", player.identity()); + data.current_player = Some(player); + break; + } else { + data.current_player = None; + debug!("update_players: No acceptable player found!"); + } + } } - } -} \ No newline at end of file +}