about summary refs log tree commit diff stats
path: root/pkgs/by-name/mp
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-01-27 19:34:10 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-01-27 19:34:10 +0100
commit875e4a25562d35ebee395726e590765fad0e8837 (patch)
treeaa65d9398c07aac5902a484e9d7b8b27f44d9a33 /pkgs/by-name/mp
parentpkgs/mpdpopm: Remove the whole message handling code (diff)
downloadnixos-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.rs38
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters.lalrpop51
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters_ast.rs47
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/lib.rs3
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/storage/mod.rs4
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>> {