diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-25 17:14:31 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-01-25 17:14:31 +0100 |
| commit | 218f0c418e71fea27e6a3045aab66e85726426c7 (patch) | |
| tree | 90f9928927a7f0853f71d89f35247e6866309a44 /pkgs/by-name/mp/mpdpopm/src/ratings.rs | |
| parent | modules/river/keymap: Provide access to rate songs, bad/good (diff) | |
| download | nixos-config-218f0c418e71fea27e6a3045aab66e85726426c7.zip | |
pkgs/mpdpopm: Make the rating centered around 0 (i.e. a i8 instead of u8)
This allows us to correctly track "negative" ratings, when the user specifies `rating decr` multiple times.
Diffstat (limited to '')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/ratings.rs | 195 |
1 files changed, 0 insertions, 195 deletions
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(); - } -} |
