started documentation effort

This commit is contained in:
Djairo Hougee 2023-05-11 14:05:59 +02:00
parent 2c433398e0
commit bc1a1c00a4
4 changed files with 106 additions and 9 deletions

View File

@ -1,10 +1,10 @@
//! This file contains all driver code for the program.
use core::time; use core::time;
use std::process::exit;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::thread; use std::thread;
use clap::Parser; use clap::Parser;
use log::{error, info}; use log::{error, info, warn};
use mpris::PlayerFinder; use mpris::PlayerFinder;
use structs::cli::Cli; use structs::cli::Cli;
use structs::{config::Config, data::Data}; use structs::{config::Config, data::Data};
@ -19,13 +19,18 @@ mod print_text;
mod structs; mod structs;
mod print_players; mod print_players;
/// This function deals with an incoming (USR1) signal.
/// It is hard-coded to play/payse the active player.
///
/// input:
/// data: Data struct for active configuration.
fn handle_signal(data: &Data) { fn handle_signal(data: &Data) {
if let Some(p) = &data.current_player { if let Some(p) = &data.current_player {
match p.checked_play_pause() { match p.checked_play_pause() {
Ok(b) => { Ok(b) => {
match b { match b {
true => info!("Player play/paused succesfully!"), true => info!("Player play/paused succesfully!"),
false => info!("Failed to send play/pause signal!"), false => warn!("Failed to send play/pause signal!"),
} }
}, },
Err(e) => error!("{e}"), Err(e) => error!("{e}"),
@ -33,26 +38,35 @@ fn handle_signal(data: &Data) {
} }
} }
/// This function contains the default maim loop body of the program.
/// It updates the active player, updates the output strings based on this, and finally formats and outputs these strings to stdout.
///
/// input:
/// pf: PlayerFinder instance for the program
/// cfg: Configuration of the program
/// data: mutable Data struct, active state of the program
/// r: pre-computed rating strings
fn default_loop(pf: &PlayerFinder, cfg: &Config, data: &mut Data, r: &Vec<String>) { fn default_loop(pf: &PlayerFinder, cfg: &Config, data: &mut Data, r: &Vec<String>) {
update_players(pf, cfg, data); update_players(pf, cfg, data);
update_message(cfg, data, r); update_message(cfg, data, r);
print_text(cfg, data); print_text(cfg, data);
} }
/// Main function. Mostly concerned with initialisation.
fn main() { fn main() {
// logging initialisation
std::env::set_var("RUST_LOG", "error"); std::env::set_var("RUST_LOG", "error");
if let Err(e) = env_logger::init() { if let Err(e) = env_logger::init() {
error!("{e}"); error!("{e}");
return return
} }
// Cli flags, Config, Data, and PlayerFinder initialisation
let cli = Cli::parse(); let cli = Cli::parse();
match confy::load::<Config>("polybar-now-playing", cli.config_file.as_str()) { match confy::load::<Config>("polybar-now-playing", cli.config_file.as_str()) {
Ok(cfg) => { Ok(cfg) => {
let mut data: Data = Data::default(); let mut data: Data = Data::default();
let rating_strings = cfg.build_rating_strings(); let rating_strings = cfg.build_rating_strings();
let term = Arc::new(AtomicBool::new(false));
let pf: PlayerFinder; let pf: PlayerFinder;
match PlayerFinder::new() { match PlayerFinder::new() {
@ -63,11 +77,14 @@ fn main() {
}, },
} }
// signal interception initialisation
let term = Arc::new(AtomicBool::new(false));
if let Err(e) = signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term)) { if let Err(e) = signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term)) {
error!("{e}"); error!("{e}");
return return
} }
// main body loop
loop { loop {
thread::sleep(time::Duration::from_millis(cfg.update_delay)); thread::sleep(time::Duration::from_millis(cfg.update_delay));
match cli.debug { match cli.debug {

View File

@ -1,9 +1,10 @@
//! This file contains structs and functionality that are relevant to the Command Line Interface part of the program.
use clap::Parser; use clap::Parser;
/// Program which finds the active mpris player and displays metadata about the playing piece of media. /// Program which finds the active mpris player and displays metadata about the playing piece of media.
/// ///
/// This program is intended to be used with polybar. /// This program is intended to be used with polybar.
/// as such, most configuration is done through config files. /// As such, most configuration is done through config files.
#[derive(Parser)] #[derive(Parser)]
pub struct Cli { pub struct Cli {
/// The name of the config file to use. /// The name of the config file to use.

View File

@ -1,39 +1,76 @@
//! This file contains structs and functions concerning themselves with the configuration of the program.
use std::{collections::HashMap}; use std::{collections::HashMap};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
/// This struct represents one metadata field to be rendered, as well as the maximum length of its' output.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Field { pub struct Field {
/// The name of the metadata field.
pub field: String, pub field: String,
/// The maximum length of the metadata field's output.
pub num_chars: u8 pub num_chars: u8
} }
impl Field { impl Field {
/// Create a new field from given values.
/// input:
/// field: name of the field
/// num_chars: maximum length of the field
///
/// returns:
/// a new Field with the given parameters.
fn new(field: String, num_chars: u8) -> Self { fn new(field: String, num_chars: u8) -> Self {
Field { Field {
field, field,
num_chars num_chars
} }
} }
pub fn constructor(metadata_field: &str, num_chars: u8) -> Self {
Self::new(metadata_field.to_owned(), num_chars) /// Create a new field from given values.
/// input:
/// field: name of the field
/// num_chars: maximum length of the field
///
/// returns:
/// a new Field with the given parameters.
pub fn constructor(field: &str, num_chars: u8) -> Self {
Self::new(field.to_owned(), num_chars)
} }
} }
/// This struct contains the 3 symbols used to represent a given userRating in a media field.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Rating { pub struct Rating {
/// character for an empty token
pub nil: char, pub nil: char,
/// character for a half token
pub half: char, pub half: char,
/// character for a full token
pub full: char pub full: char
} }
impl Rating { impl Rating {
/// This function repeats a given character n times, interspersing each occurence with a space.
/// It's kinda unwieldy here, but this is the least odd place to put it.
///
/// input:
/// c: character to repeat
/// n: number of times to repeat the character
///
/// output:
/// string of the form '<c> '{n}
fn repeat(c: char, n: usize) -> String { fn repeat(c: char, n: usize) -> String {
let mut s = c.to_string(); let mut s = c.to_string();
s.push(' '); s.push(' ');
s.repeat(n) s.repeat(n)
} }
/// As there are only a small, run-time defined variances on possible ratings (from 5 empty tokens to 5 full ones),
/// this function computes all these strings during initialization. This saves a near negligble amount of operations during run-time.
///
/// output:
/// Vec of Strings representing all possible rating configurations
fn build_rating_strings(&self) -> Vec<String> { fn build_rating_strings(&self) -> Vec<String> {
let mut out = Vec::new(); let mut out = Vec::new();
out.push(Self::repeat(self.nil, 5)); out.push(Self::repeat(self.nil, 5));
@ -51,6 +88,8 @@ impl Rating {
} }
} }
/// Defaults for Rating struct.
/// uses UTF-8, ASCII compatible tokens.
impl Default for Rating { impl Default for Rating {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -61,21 +100,41 @@ impl Default for Rating {
} }
} }
/// This struct contains all possible configuration fields.
/// It should not be used as mutable; all data in this struct should effectively be treated as read-only.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
/// Whether to hide the last output if there are currently no accepted players.
pub hide_output: bool, pub hide_output: bool,
/// Whether to apply 'fuzzy' cutoff to strings exceeding their maximum lenght.
pub fuzzy: bool, pub fuzzy: bool,
/// Whether to render the prefix at all.
pub render_prefix: bool, pub render_prefix: bool,
/// Time in milliseconds to wait between loops of the program.
pub update_delay: u64, pub update_delay: u64,
/// String to insert between different metadata fields.
pub metadata_separator: String, pub metadata_separator: String,
/// Character to insert between Array values (used when a MetadataVaue is of type Vec (ie multiple artists on one track)).
pub array_separator: char, pub array_separator: char,
/// Character to insert when a string is truncated. None implies no cut off character is inserted and the strings are truncated as is.
pub break_character: Option<char>, pub break_character: Option<char>,
/// Vec of mpris identities, describing what players are considered acceptable.
/// Prioritised based on vec index (closer to 0 -> higher priority).
pub player_priorities: Vec<String>, pub player_priorities: Vec<String>,
/// Characters to use for the xesam:userRating field.
/// If None, default values are used ('-', '/', '+').
pub rating_icons: Option<Rating>, pub rating_icons: Option<Rating>,
/// Vec of Fields. Each field represents one metadata_string to be shown in output, as well as the maximum number of characters for this field.
/// Output is shown based on Vec index (vec[0] first, vec[1] second, etc).
pub metadata_fields: Vec<Field>, pub metadata_fields: Vec<Field>,
/// Hashmap which maps Player Identities (strings; key) to prefixes (char; value).
/// If left blank all players will use the default prefix character ('>').
pub player_prefixes: HashMap<String, char>, pub player_prefixes: HashMap<String, char>,
} }
/// Defaults for the Config struct.
/// This is generated when a non-existant config file is specified in the command line.
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
@ -95,6 +154,10 @@ impl Default for Config {
} }
impl Config { impl Config {
/// This function returns the index of a given player identity in the player_priorities hashmap.
/// If the given identity is not in the map, the value of i32::MAX is returned instead.
///
/// TODO: using a HashMap would be more efficient i think.
pub fn find_player_priorities_idx(&self, name: &str) -> i32 { pub fn find_player_priorities_idx(&self, name: &str) -> i32 {
match self.player_priorities.iter() match self.player_priorities.iter()
.position(|x| x.eq(&name)) { .position(|x| x.eq(&name)) {
@ -103,6 +166,7 @@ impl Config {
} }
} }
/// This function builds the pre-computed rating strings for a given Rating_icons field.
pub fn build_rating_strings(&self) -> Vec<String> { pub fn build_rating_strings(&self) -> Vec<String> {
match self.rating_icons.as_ref() { match self.rating_icons.as_ref() {
Some(r) => r.build_rating_strings(), Some(r) => r.build_rating_strings(),
@ -110,6 +174,8 @@ impl Config {
} }
} }
/// This function returns the default player_priorities, used when a non-existent config file is requested.
/// The values of these are based on nothing but my own experience; in fact I'm not even sure if the Spotify app's identity is correct.
fn default_player_priorities() -> Vec<String> { fn default_player_priorities() -> Vec<String> {
vec![ vec![
"Clementine".to_owned(), "Clementine".to_owned(),
@ -121,6 +187,8 @@ impl Config {
] ]
} }
/// This function returns the default metadata fields, used when a non-existent config file is requested.
/// It contains the "title" and "artist" fields, with 40 and 20 maximum characters respectively.
fn default_metadata_fields() -> Vec<Field> { fn default_metadata_fields() -> Vec<Field> {
vec![ vec![
Field::constructor("xesam:title", 40), Field::constructor("xesam:title", 40),
@ -128,6 +196,8 @@ impl Config {
] ]
} }
/// This function returns the default prefixes, used when a non-existent config file is requested.
/// Like the player priorities function, this is mostly just based on my own experience.
fn default_player_prefixes() -> HashMap<String, char> { fn default_player_prefixes() -> HashMap<String, char> {
let mut out: HashMap<String, char> = HashMap::new(); let mut out: HashMap<String, char> = HashMap::new();

View File

@ -1,13 +1,22 @@
//! This file contains structs and functions related to data management within the program.
//! It effectively contains the state of the program.
use std::collections::HashMap; use std::collections::HashMap;
use mpris::Player; use mpris::Player;
/// This struct concerns itself with the current state of the program.
pub struct Data { pub struct Data {
/// Represents the media player marked as active.
/// Should be None when no (accepted) players are active.
pub current_player: Option<Player>, pub current_player: Option<Player>,
/// HashMap representing the current output strings for each configured field.
pub field_text: HashMap<String, String>, pub field_text: HashMap<String, String>,
/// What character to render as prefix.
pub prefix: char, pub prefix: char,
} }
/// Defaults for Data struct.
/// Generates an empty hashmap, and prefix, as well as None for current_player.
impl Default for Data { impl Default for Data {
fn default() -> Self { fn default() -> Self {
Self { Self {