diff options
Diffstat (limited to 'pkgs/by-name/mp')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs | 6 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/lib.rs | 1 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/messages.rs | 197 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/ratings.rs | 195 | ||||
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/storage/mod.rs | 16 |
5 files changed, 11 insertions, 404 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs index 04760f18..82a354d6 100644 --- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs +++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs @@ -160,7 +160,7 @@ async fn get_ratings( tracks: Option<Vec<String>>, with_uri: bool, ) -> Result<()> { - let mut ratings: Vec<(String, u8)> = Vec::new(); + let mut ratings: Vec<(String, i8)> = Vec::new(); for file in map_tracks(client, tracks).await? { let rating = rating_count::get(client, &file) @@ -185,7 +185,7 @@ async fn get_ratings( } /// Rate a track -async fn set_rating(client: &mut Client, rating: u8, arg: Option<String>) -> Result<()> { +async fn set_rating(client: &mut Client, rating: i8, arg: Option<String>) -> Result<()> { let is_current = arg.is_none(); let file = provide_file(client, arg).await?; @@ -472,7 +472,7 @@ enum RatingCommand { /// 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)] - Set { rating: u8, track: Option<String> }, + Set { rating: i8, track: Option<String> }, /// increment the rating for one track /// diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs index 26645228..e4579db2 100644 --- a/pkgs/by-name/mp/mpdpopm/src/lib.rs +++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs @@ -37,7 +37,6 @@ pub mod config; pub mod filters_ast; pub mod messages; pub mod playcounts; -pub mod ratings; pub mod storage; pub mod vars; diff --git a/pkgs/by-name/mp/mpdpopm/src/messages.rs b/pkgs/by-name/mp/mpdpopm/src/messages.rs index ae356f34..c7c295c8 100644 --- a/pkgs/by-name/mp/mpdpopm/src/messages.rs +++ b/pkgs/by-name/mp/mpdpopm/src/messages.rs @@ -52,8 +52,6 @@ use crate::{ clients::{Client, IdleClient, PlayerStatus}, filters::ExpressionParser, filters_ast::{FilterStickerNames, evaluate}, - ratings::{RatedTrack, RatingRequest}, - storage::{self, last_played, play_count, rating_count}, }; use backtrace::Backtrace; @@ -61,10 +59,8 @@ use boolinator::Boolinator; use tracing::debug; use std::collections::VecDeque; -use std::convert::TryFrom; use std::path::PathBuf; - #[derive(Debug)] pub enum Error { BadPath { @@ -347,15 +343,7 @@ impl MessageProcessor { state: &PlayerStatus, stickers: &FilterStickerNames<'a>, ) -> Result<()> { - if let Some(stripped) = msg.strip_prefix("rate ") { - self.rate(stripped, client, state).await - } else if let Some(stripped) = msg.strip_prefix("inc-rate ") { - self.inc_rate(stripped, client, state).await - } else if let Some(stripped) = msg.strip_prefix("setpc ") { - self.setpc(stripped, client, state).await - } else if let Some(stripped) = msg.strip_prefix("setlp ") { - self.setlp(stripped, client, state).await - } else if let Some(stripped) = msg.strip_prefix("findadd ") { + if let Some(stripped) = msg.strip_prefix("findadd ") { self.findadd(stripped.to_string(), client, stickers, state) .await } else if let Some(stripped) = msg.strip_prefix("searchadd ") { @@ -366,189 +354,6 @@ impl MessageProcessor { } } - /// Handle rating message: "RATING( TRACK)?" - async fn rate(&self, msg: &str, client: &mut Client, state: &PlayerStatus) -> Result<()> { - let req = RatingRequest::try_from(msg).map_err(|err| Error::Ratings { - source: storage::Error::Rating { - source: err, - back: Backtrace::new(), - }, - back: Backtrace::new(), - })?; - - let pathb = match req.track { - RatedTrack::Current => match state { - PlayerStatus::Stopped => { - return Err(Error::PlayerStopped {}); - } - PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr.file.clone(), - }, - RatedTrack::File(p) => p, - RatedTrack::Relative(_i) => { - return Err(Error::NotImplemented { - feature: String::from("Relative track position"), - }); - } - }; - let path: &str = pathb - .to_str() - .ok_or_else(|| Error::BadPath { pth: pathb.clone() })?; - - debug!("Setting a rating of {} for `{}'.", req.rating, path); - - rating_count::set(client, path, req.rating) - .await - .map_err(|err| Error::Ratings { - source: err, - back: Backtrace::new(), - })?; - - Ok(()) - } - - /// Handle inc-rating message: "( TRACK)?" - async fn inc_rate(&self, msg: &str, client: &mut Client, state: &PlayerStatus) -> Result<()> { - let pathb = if msg.is_empty() { - // We rate the current track - match state { - PlayerStatus::Stopped => { - return Err(Error::PlayerStopped {}); - } - PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr.file.clone(), - } - } else { - PathBuf::from(msg) - }; - - let path: &str = pathb - .to_str() - .ok_or_else(|| Error::BadPath { pth: pathb.clone() })?; - - let rating = rating_count::get(client, path) - .await - .map_err(|err| Error::Ratings { - source: err, - back: Backtrace::new(), - })? - .unwrap_or(0) - .saturating_add(1); - - debug!( - "Incrementing a rating for `{}' (new value: {}).", - path, rating - ); - - rating_count::set(client, path, rating) - .await - .map_err(|err| Error::Ratings { - source: err, - back: Backtrace::new(), - })?; - - Ok(()) - } - - /// Handle `setpc': "PC( TRACK)?" - async fn setpc(&self, msg: &str, client: &mut Client, state: &PlayerStatus) -> Result<()> { - let text = msg.trim(); - let (pc, track) = match text.find(char::is_whitespace) { - Some(idx) => ( - text[..idx] - .parse::<usize>() - .map_err(|err| Error::ExpectedInt { - source: err, - text: String::from(text), - back: Backtrace::new(), - })?, - &text[idx + 1..], - ), - None => ( - text.parse::<usize>().map_err(|err| Error::ExpectedInt { - source: err, - text: String::from(text), - back: Backtrace::new(), - })?, - "", - ), - }; - let file = if track.is_empty() { - match state { - PlayerStatus::Stopped => { - return Err(Error::PlayerStopped {}); - } - PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr - .file - .to_str() - .ok_or_else(|| Error::BadPath { - pth: curr.file.clone(), - })? - .to_string(), - } - } else { - track.to_string() - }; - - play_count::set(client, &file, pc) - .await - .map_err(|err| Error::Playcount { - source: err, - back: Backtrace::new(), - })?; - - Ok(()) - } - - /// Handle `setlp': "LASTPLAYED( TRACK)?" - async fn setlp(&self, msg: &str, client: &mut Client, state: &PlayerStatus) -> Result<()> { - let text = msg.trim(); - let (lp, track) = match text.find(char::is_whitespace) { - Some(idx) => ( - text[..idx] - .parse::<u64>() - .map_err(|err| Error::ExpectedInt { - source: err, - text: String::from(text), - back: Backtrace::new(), - })?, - &text[idx + 1..], - ), - None => ( - text.parse::<u64>().map_err(|err| Error::ExpectedInt { - source: err, - text: String::from(text), - back: Backtrace::new(), - })?, - "", - ), - }; - - let file = if track.is_empty() { - match state { - PlayerStatus::Stopped => { - return Err(Error::PlayerStopped {}); - } - PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr - .file - .to_str() - .ok_or_else(|| Error::BadPath { - pth: curr.file.clone(), - })? - .to_string(), - } - } else { - track.to_string() - }; - - last_played::set(client, &file, lp) - .await - .map_err(|err| Error::Playcount { - source: err, - back: Backtrace::new(), - })?; - - Ok(()) - } - /// Handle `findadd': "FILTER [sort TYPE] [window START:END]" async fn findadd<'a>( &self, diff --git a/pkgs/by-name/mp/mpdpopm/src/ratings.rs b/pkgs/by-name/mp/mpdpopm/src/ratings.rs deleted file mode 100644 index 739d3827..00000000 --- a/pkgs/by-name/mp/mpdpopm/src/ratings.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com> -// -// This file is part of mpdpopm. -// -// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU -// General Public License as published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even -// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -// Public License for more details. -// -// You should have received a copy of the GNU General Public License along with mpdpopm. If not, -// see <http://www.gnu.org/licenses/>. - -//! Logic for rating MPD tracks. -//! -//! # Introduction -//! -//! This module contains types implementing a basic rating functionality for -//! [MPD](http://www.musicpd.org). -//! -//! # Discussion -//! -//! Rating messages to the relevant channel take the form `RATING( TRACK)?` (the two components can -//! be separated by any whitespace). The rating can be given by an integer between 0 & 255 -//! (inclusive) represented in base ten, or as one-to-five asterisks (i.e. `\*{1,5}`). In the latter -//! case, the rating will be mapped to 1-255 as per Winamp's -//! [convention](http://forums.winamp.com/showpost.php?p=2903240&postcount=94): -//! -//! - 224-255: 5 stars when READ with windows explorer, writes 255 -//! - 160-223: 4 stars when READ with windows explorer, writes 196 -//! - 096-159: 3 stars when READ with windows explorer, writes 128 -//! - 032-095: 2 stars when READ with windows explorer, writes 64 -//! - 001-031: 1 stars when READ with windows explorer, writes 1 -//! -//! NB a rating of zero means "not rated". -//! -//! Everything after the first whitepace, if present, is taken to be the track to be rated (i.e. -//! the track may contain whitespace). If omitted, the rating is taken to apply to the current -//! track. - -use backtrace::Backtrace; - -use std::path::PathBuf; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Error // -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// An enumeration of ratings errors -#[derive(Debug)] -pub enum Error { - Rating { - source: std::num::ParseIntError, - text: String, - }, - PlayerStopped, - NotImplemented { - feature: String, - }, - BadPath { - pth: PathBuf, - back: Backtrace, - }, - Client { - source: crate::clients::Error, - back: Backtrace, - }, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Error::Rating { source, text } => write!( - f, - "Unable to interpret ``{}'' as a rating: {}", - text, source - ), - Error::PlayerStopped => write!(f, "Player stopped"), - Error::NotImplemented { feature } => write!(f, "{} not implemented", feature), - Error::BadPath { pth, back: _ } => write!(f, "Bad path: {:?}", pth), - Error::Client { source, back: _ } => write!(f, "Client error: {}", source), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match &self { - Error::Rating { text: _, source } => Some(source), - Error::Client { source, back: _ } => Some(source), - _ => None, - } - } -} - -pub type Result<T> = std::result::Result<T, Error>; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// RatingRequest message // -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// The track to which a rating shall be applied. -#[derive(Debug, PartialEq)] -pub enum RatedTrack { - Current, - File(std::path::PathBuf), - Relative(i8), -} - -/// A request from a client to rate a track. -#[derive(Debug)] -pub struct RatingRequest { - pub rating: u8, - pub track: RatedTrack, -} - -/// Produce a RatingRequest instance from a line of MPD output. -impl std::convert::TryFrom<&str> for RatingRequest { - type Error = Error; - - /// Attempt to produce a RatingRequest instance from a line of MPD response to a - /// "readmessages" command. After the channel line, each subsequent line will be of the form - /// "message: $MESSAGE"-- this method assumes that the "message: " prefix has been stripped off - /// (i.e. we're dealing with a single line of text containing only our custom message format). - /// - /// For ratings, we expect a message of the form: "RATING (TRACK)?". - fn try_from(text: &str) -> std::result::Result<Self, Self::Error> { - // We expect a message of the form: "RATING (TRACK)?"; let us split `text' into those two - // components for separate processing: - let text = text.trim(); - let (rating, track) = match text.find(char::is_whitespace) { - Some(idx) => (&text[..idx], &text[idx + 1..]), - None => (text, ""), - }; - - // Rating first-- the desired rating can be specified in a few ways... - let rating = if rating.is_empty() { - // an empty string is interpreted as zero: - 0u8 - } else { - // "*{1,5}" is interpreted as one-five stars, mapped to [0,255] as per Winamp: - match rating { - "*" => 1, - "**" => 64, - "***" => 128, - "****" => 196, - "*****" => 255, - // failing that, we try just interperting `rating' as an unsigned integer: - _ => rating.parse::<u8>().map_err(|err| Error::Rating { - source: err, - text: String::from(rating), - })?, - } - }; - - // Next-- track. This, too, can be given in a few ways: - let track = if track.is_empty() { - // nothing at all just means "current track" - RatedTrack::Current - } else { - // otherwise... - match text.parse::<i8>() { - // if we can interpret `track' as an i8, we take it as an offset... - Ok(i) => RatedTrack::Relative(i), - // else, we assume it's a path. If it's not, we'll figure that out downstream. - Err(_) => RatedTrack::File(std::path::PathBuf::from(&track)), - } - }; - - Ok(RatingRequest { rating, track }) - } -} - -#[cfg(test)] -mod rating_request_tests { - use super::*; - use std::convert::TryFrom; - - /// RatingRequest smoke tests - #[test] - fn rating_request_smoke() { - let req = RatingRequest::try_from("*** foo bar splat.mp3").unwrap(); - assert_eq!(req.rating, 128); - assert_eq!( - req.track, - RatedTrack::File(PathBuf::from("foo bar splat.mp3")) - ); - let req = RatingRequest::try_from("255").unwrap(); - assert_eq!(req.rating, 255); - assert_eq!(req.track, RatedTrack::Current); - let _req = RatingRequest::try_from("******").unwrap_err(); - } -} diff --git a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs index 325b633a..d64f17c1 100644 --- a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs +++ b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs @@ -16,10 +16,6 @@ pub enum Error { source: crate::clients::Error, back: Backtrace, }, - Rating { - source: crate::ratings::Error, - back: Backtrace, - }, } impl std::fmt::Display for Error { @@ -31,7 +27,6 @@ impl std::fmt::Display for Error { write!(f, "Couldn't get system time: {}", source) } Error::Client { source, back: _ } => write!(f, "Client error: {}", source), - Error::Rating { source, back: _ } => write!(f, "Rating error: {}", source), } } } @@ -93,7 +88,10 @@ pub mod play_count { #[tokio::test] async fn pc_smoke() { let mock = Box::new(Mock::new(&[ - ("sticker get song a unwoundstack.com:playcount", "sticker: unwoundstack.com:playcount=11\nOK\n"), + ( + "sticker get song a unwoundstack.com:playcount", + "sticker: unwoundstack.com:playcount=11\nOK\n", + ), ( "sticker get song a unwoundstack.com:playcount", "ACK [50@0] {sticker} no such sticker\n", @@ -188,9 +186,9 @@ pub mod rating_count { pub const STICKER: &str = "unwoundstack.com:ratings_count"; /// Retrieve the rating count for a track - pub async fn get(client: &mut Client, file: &str) -> Result<Option<u8>> { + pub async fn get(client: &mut Client, file: &str) -> Result<Option<i8>> { client - .get_sticker::<u8>(file, STICKER) + .get_sticker::<i8>(file, STICKER) .await .map_err(|err| Error::Client { source: err, @@ -199,7 +197,7 @@ pub mod rating_count { } /// Set the rating count for a track - pub async fn set(client: &mut Client, file: &str, rating_count: u8) -> Result<()> { + pub async fn set(client: &mut Client, file: &str, rating_count: i8) -> Result<()> { client .set_sticker(file, STICKER, &format!("{}", rating_count)) .await |
