about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-01-25 17:08:18 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-01-25 17:08:18 +0100
commita5d8629d99aef14f24517d2014879af6aa43cf71 (patch)
treec21a6babcac121e142ed7bf62ecd65f43565bca2
parentmodules/{mpdpopm,legacy/beets}: Move the mpd stat tracking to mpdpopm (diff)
downloadnixos-config-a5d8629d99aef14f24517d2014879af6aa43cf71.zip
modules/river/keymap: Provide access to rate songs, bad/good
Diffstat (limited to '')
-rw-r--r--modules/by-name/ri/river/keymap.nix2
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs267
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/messages.rs1
3 files changed, 170 insertions, 100 deletions
diff --git a/modules/by-name/ri/river/keymap.nix b/modules/by-name/ri/river/keymap.nix
index ca7ab69d..48aca3cd 100644
--- a/modules/by-name/ri/river/keymap.nix
+++ b/modules/by-name/ri/river/keymap.nix
@@ -79,6 +79,8 @@ in {
           "n" = mkSpawn pkgs.mpp "next" {};
           "p" = mkSpawn pkgs.mpp "prev" {};
           "t" = mkSpawn pkgs.mpp "toggle" {};
+          "g" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating inc" {};
+          "b" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating decr" {};
         };
 
         # Select tags for view.
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
index 4ffcd499..04760f18 100644
--- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
@@ -79,7 +79,6 @@ pub enum Error {
 }
 
 impl fmt::Display for Error {
-    #[allow(unreachable_patterns)] // the _ arm is *currently* unreachable
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
         match self {
             Error::NoSubCommand => write!(f, "No sub-command given"),
@@ -122,7 +121,18 @@ async fn map_tracks(client: &mut Client, args: Option<Vec<String>>) -> Result<Ve
     let files = match args {
         Some(iter) => iter,
         None => {
-            let file = match client.status().await.map_err(|err| Error::Client {
+            let file = provide_file(client, None).await?;
+            vec![file]
+        }
+    };
+    Ok(files)
+}
+
+async fn provide_file(client: &mut Client, maybe_file: Option<String>) -> Result<String> {
+    let file = match maybe_file {
+        Some(file) => file,
+        None => {
+            match client.status().await.map_err(|err| Error::Client {
                 source: err,
                 back: Backtrace::new(),
             })? {
@@ -137,11 +147,11 @@ async fn map_tracks(client: &mut Client, args: Option<Vec<String>>) -> Result<Ve
                 PlayerStatus::Stopped => {
                     return Err(Error::PlayerStopped);
                 }
-            };
-            vec![file]
+            }
         }
     };
-    Ok(files)
+
+    Ok(file)
 }
 
 /// Retrieve ratings for one or more tracks
@@ -175,49 +185,74 @@ async fn get_ratings(
 }
 
 /// Rate a track
-async fn set_rating(
-    client: &mut Client,
-    chan: &str,
-    rating: u8,
-    arg: Option<String>,
-) -> Result<()> {
-    let cmd = match &arg {
-        Some(uri) => format!("rate {} \\\"{}\\\"", rating, uri),
-        None => format!("rate {}", rating),
-    };
-    client
-        .send_message(chan, &cmd)
+async fn set_rating(client: &mut Client, rating: u8, arg: Option<String>) -> Result<()> {
+    let is_current = arg.is_none();
+    let file = provide_file(client, arg).await?;
+
+    rating_count::set(client, &file, rating)
         .await
-        .map_err(|err| Error::Client {
-            source: err,
+        .map_err(|err| Error::Ratings {
+            source: Box::new(err),
             back: Backtrace::new(),
         })?;
 
-    match &arg {
-        Some(uri) => info!("Set the rating for \"{}\" to \"{}\".", uri, rating),
-        None => info!("Set the rating for the current song to \"{}\".", rating),
+    match is_current {
+        false => info!("Set the rating for \"{}\" to \"{}\".", file, rating),
+        true => info!("Set the rating for the current song to \"{}\".", rating),
     }
 
     Ok(())
 }
 
 /// Rate a track by incrementing the current rating
-async fn inc_rating(client: &mut Client, chan: &str, arg: Option<String>) -> Result<()> {
-    let cmd = match &arg {
-        Some(uri) => format!("inc-rate \\\"{}\\\"", uri),
-        None => "inc-rate ".to_owned(),
-    };
-    client
-        .send_message(chan, &cmd)
+async fn inc_rating(client: &mut Client, arg: Option<String>) -> Result<()> {
+    let is_current = arg.is_none();
+    let file = provide_file(client, arg).await?;
+
+    let now = rating_count::get(client, &file)
         .await
-        .map_err(|err| Error::Client {
-            source: err,
+        .map_err(|err| Error::Ratings {
+            source: Box::new(err),
+            back: Backtrace::new(),
+        })?;
+
+    rating_count::set(client, &file, now.unwrap_or_default().saturating_add(1))
+        .await
+        .map_err(|err| Error::Ratings {
+            source: Box::new(err),
             back: Backtrace::new(),
         })?;
 
-    match &arg {
-        Some(uri) => info!("Incremented the rating for \"{}\".", uri),
-        None => info!("Incremented the rating for the current song."),
+    match is_current {
+        false => info!("Incremented the rating for \"{}\".", file),
+        true => info!("Incremented the rating for the current song."),
+    }
+
+    Ok(())
+}
+
+/// Rate a track by decrementing the current rating
+async fn decr_rating(client: &mut Client, arg: Option<String>) -> Result<()> {
+    let is_current = arg.is_none();
+    let file = provide_file(client, arg).await?;
+
+    let now = rating_count::get(client, &file)
+        .await
+        .map_err(|err| Error::Ratings {
+            source: Box::new(err),
+            back: Backtrace::new(),
+        })?;
+
+    rating_count::set(client, &file, now.unwrap_or_default().saturating_sub(1))
+        .await
+        .map_err(|err| Error::Ratings {
+            source: Box::new(err),
+            back: Backtrace::new(),
+        })?;
+
+    match is_current {
+        false => info!("Decremented the rating for \"{}\".", file),
+        true => info!("Decremented the rating for the current song."),
     }
 
     Ok(())
@@ -253,27 +288,20 @@ async fn get_play_counts(
 }
 
 /// Set the playcount for a track
-async fn set_play_counts(
-    client: &mut Client,
-    chan: &str,
-    playcount: usize,
-    arg: Option<String>,
-) -> Result<()> {
-    let cmd = match &arg {
-        Some(uri) => format!("setpc {} \\\"{}\\\"", playcount, uri),
-        None => format!("setpc {}", playcount),
-    };
-    client
-        .send_message(chan, &cmd)
+async fn set_play_counts(client: &mut Client, playcount: usize, arg: Option<String>) -> Result<()> {
+    let is_current = arg.is_none();
+    let file = provide_file(client, arg).await?;
+
+    play_count::set(client, &file, playcount)
         .await
-        .map_err(|err| Error::Client {
-            source: err,
+        .map_err(|err| Error::Playcounts {
+            source: Box::new(err),
             back: Backtrace::new(),
         })?;
 
-    match &arg {
-        Some(uri) => info!("Set the playcount for \"{}\" to \"{}\".", uri, playcount),
-        None => info!(
+    match is_current {
+        false => info!("Set the playcount for \"{}\" to \"{}\".", file, playcount),
+        true => info!(
             "Set the playcount for the current song to \"{}\".",
             playcount
         ),
@@ -325,27 +353,20 @@ async fn get_last_playeds(
 }
 
 /// Set the playcount for a track
-async fn set_last_playeds(
-    client: &mut Client,
-    chan: &str,
-    lastplayed: u64,
-    arg: Option<String>,
-) -> Result<()> {
-    let cmd = match &arg {
-        Some(uri) => format!("setlp {} {}", lastplayed, uri),
-        None => format!("setlp {}", lastplayed),
-    };
-    client
-        .send_message(chan, &cmd)
+async fn set_last_playeds(client: &mut Client, lastplayed: u64, arg: Option<String>) -> Result<()> {
+    let is_current = arg.is_none();
+    let file = provide_file(client, arg).await?;
+
+    last_played::set(client, &file, lastplayed)
         .await
-        .map_err(|err| Error::Client {
-            source: err,
+        .map_err(|err| Error::Playcounts {
+            source: Box::new(err),
             back: Backtrace::new(),
         })?;
 
-    match &arg {
-        Some(uri) => info!("Set last played for \"{}\" to \"{}\".", uri, lastplayed),
-        None => info!(
+    match is_current {
+        false => info!("Set last played for \"{}\" to \"{}\".", file, lastplayed),
+        true => info!(
             "Set last played for the current song to \"{}\".",
             lastplayed
         ),
@@ -426,7 +447,7 @@ struct Args {
 }
 
 #[derive(Subcommand)]
-enum SubCommand {
+enum RatingCommand {
     /// retrieve the rating for one or more tracks
     ///
     /// With no arguments, retrieve the rating of the current song & print it
@@ -437,7 +458,7 @@ enum SubCommand {
     /// Ratings are expressed as an integer between 0 & 255, inclusive, with
     /// the convention that 0 denotes "un-rated".
     #[clap(verbatim_doc_comment)]
-    GetRating {
+    Get {
         /// Always show the song URI, even when there is only one track
         #[arg(short, long)]
         with_uri: bool,
@@ -451,15 +472,25 @@ enum SubCommand {
     /// With a second argument, rate that song at the first argument. Ratings
     /// may be expressed a an integer between 0 & 255, inclusive.
     #[clap(verbatim_doc_comment)]
-    SetRating { rating: u8, track: Option<String> },
+    Set { rating: u8, track: Option<String> },
 
     /// increment the rating for one track
     ///
     /// With one argument, increment the rating of the current song.
     /// With a second argument, rate that song at the first argument.
     #[clap(verbatim_doc_comment)]
-    IncRating { track: Option<String> },
+    Inc { track: Option<String> },
+
+    /// decrement the rating for one track
+    ///
+    /// With one argument, decrement the rating of the current song.
+    /// With a second argument, rate that song at the first argument.
+    #[clap(verbatim_doc_comment)]
+    Decr { track: Option<String> },
+}
 
+#[derive(Subcommand)]
+enum PlayCountCommand {
     /// retrieve the play count for one or more tracks
     ///
     /// With no arguments, retrieve the play count of the current song & print it
@@ -467,7 +498,7 @@ enum SubCommand {
     /// on stdout. With multiple arguments, print their play counts on stdout, one
     /// per line, prefixed by the track name.
     #[clap(verbatim_doc_comment)]
-    GetPc {
+    Get {
         /// Always show the song URI, even when there is only one track
         #[arg(short, long)]
         with_uri: bool,
@@ -480,11 +511,14 @@ enum SubCommand {
     /// With one argument, set the play count of the current song to that argument. With a
     /// second argument, set the play count for that song to the first.
     #[clap(verbatim_doc_comment)]
-    SetPc {
+    Set {
         play_count: usize,
         track: Option<String>,
     },
+}
 
+#[derive(Subcommand)]
+enum LastPlayedCommand {
     /// retrieve the last played timestamp for one or more tracks
     ///
     /// With no arguments, retrieve the last played timestamp of the current
@@ -495,7 +529,7 @@ enum SubCommand {
     ///
     /// The last played timestamp is expressed in seconds since Unix epoch.
     #[clap(verbatim_doc_comment)]
-    GetLp {
+    Get {
         /// Always show the song URI, even when there is only one track
         #[arg(short, long)]
         with_uri: bool,
@@ -509,14 +543,44 @@ enum SubCommand {
     /// arguments, set the last played time for the second argument to the first.
     /// The last played timestamp is expressed in seconds since Unix epoch.
     #[clap(verbatim_doc_comment)]
-    SetLp {
+    Set {
         last_played: u64,
         track: Option<String>,
     },
+}
 
+#[derive(Subcommand)]
+enum PlaylistsCommand {
     /// retrieve the list of stored playlists
     #[clap(verbatim_doc_comment)]
-    GetPlaylists {},
+    Get {},
+}
+
+#[derive(Subcommand)]
+enum SubCommand {
+    /// Change details about rating.
+    Rating {
+        #[command(subcommand)]
+        command: RatingCommand,
+    },
+
+    /// Change details about play count.
+    PlayCount {
+        #[command(subcommand)]
+        command: PlayCountCommand,
+    },
+
+    /// Change details about last played date.
+    LastPlayed {
+        #[command(subcommand)]
+        command: LastPlayedCommand,
+    },
+
+    /// Change details about generated playlists.
+    Playlists {
+        #[command(subcommand)]
+        command: PlaylistsCommand,
+    },
 
     /// search case-sensitively for songs matching matching a filter and add them to the queue
     ///
@@ -642,28 +706,33 @@ async fn main() -> Result<()> {
     };
 
     match args.command {
-        SubCommand::GetRating { with_uri, tracks } => {
-            get_ratings(&mut client, tracks, with_uri).await
-        }
-        SubCommand::SetRating { rating, track } => {
-            set_rating(&mut client, &config.commands_chan, rating, track).await
-        }
-        SubCommand::IncRating { track } => {
-            inc_rating(&mut client, &config.commands_chan, track).await
-        }
-        SubCommand::GetPc { with_uri, tracks } => {
-            get_play_counts(&mut client, tracks, with_uri).await
-        }
-        SubCommand::SetPc { play_count, track } => {
-            set_play_counts(&mut client, &config.commands_chan, play_count, track).await
-        }
-        SubCommand::GetLp { with_uri, tracks } => {
-            get_last_playeds(&mut client, tracks, with_uri).await
-        }
-        SubCommand::SetLp { last_played, track } => {
-            set_last_playeds(&mut client, &config.commands_chan, last_played, track).await
-        }
-        SubCommand::GetPlaylists {} => get_playlists(&mut client).await,
+        SubCommand::Rating { command } => match command {
+            RatingCommand::Get { with_uri, tracks } => {
+                get_ratings(&mut client, tracks, with_uri).await
+            }
+            RatingCommand::Set { rating, track } => set_rating(&mut client, rating, track).await,
+            RatingCommand::Inc { track } => inc_rating(&mut client, track).await,
+            RatingCommand::Decr { track } => decr_rating(&mut client, track).await,
+        },
+        SubCommand::PlayCount { command } => match command {
+            PlayCountCommand::Get { with_uri, tracks } => {
+                get_play_counts(&mut client, tracks, with_uri).await
+            }
+            PlayCountCommand::Set { play_count, track } => {
+                set_play_counts(&mut client, play_count, track).await
+            }
+        },
+        SubCommand::LastPlayed { command } => match command {
+            LastPlayedCommand::Get { with_uri, tracks } => {
+                get_last_playeds(&mut client, tracks, with_uri).await
+            }
+            LastPlayedCommand::Set { last_played, track } => {
+                set_last_playeds(&mut client, last_played, track).await
+            }
+        },
+        SubCommand::Playlists { command } => match command {
+            PlaylistsCommand::Get {} => get_playlists(&mut client).await,
+        },
         SubCommand::Findadd { filter } => {
             findadd(&mut client, &config.commands_chan, &filter, true).await
         }
diff --git a/pkgs/by-name/mp/mpdpopm/src/messages.rs b/pkgs/by-name/mp/mpdpopm/src/messages.rs
index 85b24470..ae356f34 100644
--- a/pkgs/by-name/mp/mpdpopm/src/messages.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/messages.rs
@@ -64,7 +64,6 @@ use std::collections::VecDeque;
 use std::convert::TryFrom;
 use std::path::PathBuf;
 
-////////////////////////////////////////////////////////////////////////////////////////////////////
 
 #[derive(Debug)]
 pub enum Error {