diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-27 19:34:10 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-27 19:34:10 +0100 |
| commit | 875e4a25562d35ebee395726e590765fad0e8837 (patch) | |
| tree | aa65d9398c07aac5902a484e9d7b8b27f44d9a33 /pkgs/by-name/mp | |
| parent | pkgs/mpdpopm: Remove the whole message handling code (diff) | |
| download | nixos-config-875e4a25562d35ebee395726e590765fad0e8837.zip | |
pkgs/mpdpopm: Provide full access queries via the `searchadd` command prime
Diffstat (limited to 'pkgs/by-name/mp')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs | 38 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/filters.lalrpop | 51 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/filters_ast.rs | 47 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/lib.rs | 3 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/storage/mod.rs | 4 |
5 files changed, 77 insertions, 66 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs index 746088ca..82272aeb 100644 --- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs +++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs @@ -27,7 +27,7 @@ //! for `mppopmd`. Run `mppopm --help` for detailed usage. use mpdpopm::{ - clients::{Client, PlayerStatus, quote}, + clients::{Client, PlayerStatus}, config::{self, Config}, filters::ExpressionParser, filters_ast::{FilterStickerNames, evaluate}, @@ -266,10 +266,7 @@ async fn get_playlists(client: &mut Client) -> Result<()> { /// Add songs selected by filter to the queue async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> Result<()> { - let qfilter = quote(filter); - debug!("findadd: got ``{}'', quoted to ``{}''.", filter, qfilter); - - let ast = match ExpressionParser::new().parse(&qfilter) { + let ast = match ExpressionParser::new().parse(filter) { Ok(ast) => ast, Err(err) => { bail!("Failed to parse filter: `{}`", err) @@ -283,7 +280,13 @@ async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> R .await .context("Failed to evaluate filter")? { - results.push(client.add(&song).await); + let out = client.add(&song).await; + + if out.is_ok() { + eprintln!("Added: `{}`", song) + } + + results.push(out); } match results.into_iter().collect::<Result<Vec<()>>>() { @@ -292,22 +295,6 @@ async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> R } } -/// Send an arbitrary command -async fn send_command(client: &mut Client, chan: &str, args: Vec<String>) -> Result<()> { - client - .send_message( - chan, - args.iter() - .map(String::as_str) - .map(quote) - .collect::<Vec<String>>() - .join(" ") - .as_str(), - ) - .await?; - Ok(()) -} - /// `mppopmd' client #[derive(Parser)] struct Args { @@ -493,10 +480,6 @@ enum SubCommand { #[arg(short, long, default_value_t = true)] case_sensitive: bool, }, - - /// Send a command to mpd. - #[clap(verbatim_doc_comment)] - SendCommand { args: Vec<String> }, } #[tokio::main] @@ -586,8 +569,5 @@ async fn main() -> Result<()> { filter, case_sensitive, } => searchadd(&mut client, &filter, case_sensitive).await, - SubCommand::SendCommand { args } => { - send_command(&mut client, &config.commands_chan, args).await - } } } diff --git a/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop index a591a3ba..0c12f59b 100644 --- a/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop +++ b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop @@ -15,8 +15,18 @@ use lalrpop_util::ParseError; -use crate::filters_ast::{Conjunction, Disjunction, Expression, OpCode, Selector, Term, Value, - expect_quoted, parse_iso_8601}; +use crate::filters_ast::{ + Conjunction, + Disjunction, + Expression, + OpCode, + Selector, + Term, + Value, + expect_quoted, + parse_iso_8601 +}; +use tracing::debug; grammar; @@ -63,20 +73,27 @@ pub ExprSel: Selector = { r"(?i)rating" => Selector::Rating, r"(?i)playcount" => Selector::PlayCount, r"(?i)lastplayed" => Selector::LastPlayed, + r"(?i)skipped" => Selector::Skipped, }; pub Token: Value = { - <s:r"[0-9]+"> =>? { - eprintln!("matched token: ``{}''.", s); + <s:r"(-)?[0-9]+"> =>? { + debug!("matched token: ``{}''.", s); // We need to yield a Result<Value, ParseError> match s.parse::<usize>() { Ok(n) => Ok(Value::Uint(n)), - Err(_) => Err(ParseError::User { - error: "Internal parse error while parsing unsigned int" }) + Err(_) => match s.parse::<i64>() { + Ok(n) => Ok(Value::Int(n)), + Err(_) => Err( + ParseError::User { + error: "Internal parse error while parsing unsigned int" + } + ) + } } }, <s:r#""([ \t'a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\"|\\')+""#> => { - eprintln!("matched token: ``{}''.", s); + debug!("matched token: ``{}''.", s); let s = expect_quoted(s).unwrap(); match parse_iso_8601(&mut s.as_bytes()) { Ok(x) => Value::UnixEpoch(x), @@ -84,7 +101,7 @@ pub Token: Value = { } }, <s:r#"'([ \t"a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\'|\\")+'"#> => { - eprintln!("matched token: ``{}''.", s); + debug!("matched token: ``{}''.", s); let s = expect_quoted(s).unwrap(); match parse_iso_8601(&mut s.as_bytes()) { Ok(x) => Value::UnixEpoch(x), @@ -95,49 +112,49 @@ pub Token: Value = { pub Term: Box<Term> = { <t:ExprSel> <u:Token> => { - eprintln!("matched unary condition: ``({}, {:#?})''", t, u); + debug!("matched unary condition: ``({}, {:#?})''", t, u); Box::new(Term::UnaryCondition(t, u)) }, <t:ExprSel> <o:ExprOp> <u:Token> => { - eprintln!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u); + debug!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u); Box::new(Term::BinaryCondition(t, o, u)) }, } pub Conjunction: Box<Conjunction> = { <e1:Expression> "AND" <e2:Expression> => { - eprintln!("matched conjunction: ``({:#?}, {:#?})''", e1, e2); + debug!("matched conjunction: ``({:#?}, {:#?})''", e1, e2); Box::new(Conjunction::Simple(e1, e2)) }, <c:Conjunction> "AND" <e:Expression> => { - eprintln!("matched conjunction: ``({:#?}, {:#?})''", c, e); + debug!("matched conjunction: ``({:#?}, {:#?})''", c, e); Box::new(Conjunction::Compound(c, e)) }, } pub Disjunction: Box<Disjunction> = { <e1:Expression> "OR" <e2:Expression> => { - eprintln!("matched disjunction: ``({:#?}, {:#?})''", e1, e2); + debug!("matched disjunction: ``({:#?}, {:#?})''", e1, e2); Box::new(Disjunction::Simple(e1, e2)) }, <c:Disjunction> "OR" <e:Expression> => { - eprintln!("matched disjunction: ``({:#?}, {:#?})''", c, e); + debug!("matched disjunction: ``({:#?}, {:#?})''", c, e); Box::new(Disjunction::Compound(c, e)) }, } pub Expression: Box<Expression> = { "(" <t:Term> ")" => { - eprintln!("matched parenthesized term: ``({:#?})''", t); + debug!("matched parenthesized term: ``({:#?})''", t); Box::new(Expression::Simple(t)) }, "(" "!" <e:Expression> ")" => Box::new(Expression::Negation(e)), "(" <c:Conjunction> ")" => { - eprintln!("matched parenthesized conjunction: ``({:#?})''", c); + debug!("matched parenthesized conjunction: ``({:#?})''", c); Box::new(Expression::Conjunction(c)) }, "(" <c:Disjunction> ")" => { - eprintln!("matched parenthesized disjunction: ``({:#?})''", c); + debug!("matched parenthesized disjunction: ``({:#?})''", c); Box::new(Expression::Disjunction(c)) }, } diff --git a/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs index bd1a67d6..8b8c2696 100644 --- a/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs +++ b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs @@ -18,7 +18,7 @@ //! This module provides support for our [lalrpop](https://github.com/lalrpop/lalrpop) grammar. use crate::clients::Client; -use crate::storage::{last_played, play_count, rating_count}; +use crate::storage::{last_played, play_count, rating_count, skipped}; use anyhow::{Context, Error, Result, anyhow, bail}; use boolinator::Boolinator; @@ -95,6 +95,7 @@ pub enum Selector { Rating, PlayCount, LastPlayed, + Skipped, } impl std::fmt::Display for Selector { @@ -133,6 +134,7 @@ impl std::fmt::Display for Selector { Selector::Rating => "rating", Selector::PlayCount => "playcount", Selector::LastPlayed => "lastplayed", + Selector::Skipped => "skipped", } ) } @@ -143,6 +145,7 @@ pub enum Value { Text(String), UnixEpoch(i64), Uint(usize), + Int(i64), } fn quote_value(x: &Value) -> String { @@ -166,6 +169,9 @@ fn quote_value(x: &Value) -> String { Value::Uint(n) => { format!("'{}'", n) } + Value::Int(n) => { + format!("'{}'", n) + } } } @@ -655,6 +661,7 @@ async fn eval_numeric_sticker_term< .for_each(|song| { m.entry(song).or_insert(default_val); }); + // Now that we don't have to worry about operations that can fail, we can use // `filter_map'. Ok(m.drain() @@ -674,6 +681,7 @@ pub struct FilterStickerNames<'a> { rating: &'a str, playcount: &'a str, lastplayed: &'a str, + skipped: &'a str, } impl FilterStickerNames<'static> { @@ -688,6 +696,7 @@ impl Default for FilterStickerNames<'static> { rating: rating_count::STICKER, playcount: play_count::STICKER, lastplayed: last_played::STICKER, + skipped: skipped::STICKER, } } } @@ -711,18 +720,19 @@ async fn eval_term<'a>( .collect()), Term::BinaryCondition(attr, op, val) => { if *attr == Selector::Rating { - match val { - Value::Uint(n) => { - if *n > 255 { - bail!("Rating of `{}` is greater than allowed!", n) - } - Ok( - eval_numeric_sticker_term(stickers.rating, client, *op, *n as u8, 0) - .await?, - ) - } - _ => bail!("filter ratings expect an unsigned int; got {:#?}", val), - } + let value = match val { + Value::Int(n) => *n as i128, + Value::Uint(n) => *n as i128, + _ => bail!("filter ratings expect an int; got {:#?}", val), + }; + + let val: i8 = value.try_into().with_context(|| { + format!( + "Failed to convert `{}` into a number from -128 to 128!", + value + ) + })?; + Ok(eval_numeric_sticker_term(stickers.rating, client, *op, val, 0).await?) } else if *attr == Selector::PlayCount { match val { Value::Uint(n) => { @@ -731,7 +741,7 @@ async fn eval_term<'a>( .await?, ) } - _ => bail!("filter ratings expect an unsigned int; got {:#?}", val), + _ => bail!("filter play_count expect an unsigned int; got {:#?}", val), } } else if *attr == Selector::LastPlayed { match val { @@ -741,7 +751,14 @@ async fn eval_term<'a>( .await?, ) } - _ => bail!("filter ratings expect an unsigned int; got {:#?}", val), + _ => bail!("filter last_played expect an unix epoch; got {:#?}", val), + } + } else if *attr == Selector::Skipped { + match val { + Value::Uint(t) => { + Ok(eval_numeric_sticker_term(stickers.skipped, client, *op, *t, 0).await?) + } + _ => bail!("filter skipped expect an unsigned int; got {:#?}", val), } } else { Ok(client diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs index 7e1d3357..d5db57b4 100644 --- a/pkgs/by-name/mp/mpdpopm/src/lib.rs +++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs @@ -51,7 +51,6 @@ pub mod filters { use crate::{ clients::{Client, IdleClient, IdleSubSystem}, config::{Config, Connection}, - filters_ast::FilterStickerNames, playcounts::PlayState, }; @@ -68,8 +67,6 @@ use tracing::{debug, error, info}; pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> { info!("mpdpopm {} beginning.", vars::VERSION); - let filter_stickers = FilterStickerNames::new(); - let mut client = match cfg.conn { Connection::Local { ref path } => Client::open(path) diff --git a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs index 24d8dcb5..c13475ad 100644 --- a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs +++ b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs @@ -1,4 +1,4 @@ -use anyhow::{Error, Result}; +use anyhow::Result; pub mod play_count { use anyhow::Context; @@ -67,7 +67,7 @@ pub mod skipped { use super::Result; - const STICKER: &str = "unwoundstack.com:skipped_count"; + pub(crate) const STICKER: &str = "unwoundstack.com:skipped_count"; /// Retrieve the skip count for a track pub async fn get(client: &mut Client, file: &str) -> Result<Option<usize>> { |
