cmus_notify/cmus/
player_settings.rs

1use crate::cmus::{CmusError, TemplateProcessor};
2#[cfg(feature = "debug")]
3use log::{debug, info};
4use parse_display::Display;
5use std::num::ParseIntError;
6use std::str::FromStr;
7
8#[derive(PartialEq, Clone)]
9#[cfg_attr(feature = "debug", derive(Debug))]
10pub struct PlayerSettings {
11    pub repeat: bool,
12    pub repeat_current: bool,
13    pub shuffle: Shuffle,
14    pub aaa_mode: AAAMode,
15    pub volume: Volume,
16}
17
18#[derive(Display, PartialEq, Default, Clone)]
19#[cfg_attr(feature = "debug", derive(Debug))]
20pub enum Shuffle {
21    #[default]
22    Off,
23    Tracks,
24    Albums,
25}
26
27#[derive(PartialEq, Default, Clone)]
28#[cfg_attr(feature = "debug", derive(Debug))]
29pub struct Volume {
30    pub left: u8,
31    pub right: u8,
32}
33
34#[derive(Display, PartialEq, Default, Clone)]
35#[cfg_attr(feature = "debug", derive(Debug))]
36pub enum AAAMode {
37    #[default]
38    All,
39    Album,
40    Artist,
41}
42
43impl TemplateProcessor for PlayerSettings {
44    /// Replace all keys in the template with the corresponding values.
45    /// If the key is unknown, it will be replaced with an empty string.
46    /// This function should be used after the track metadata placeholders have been replaced.
47    #[inline(always)]
48    fn process(&self, template: String) -> String {
49        #[cfg(feature = "debug")]
50        {
51            info!("Processing template: {}", template);
52            debug!("Processing template with player settings: {:?}", self);
53        }
54        let mut processed = template.clone();
55
56        Self::get_keys(template.as_str()).iter().for_each(|key| {
57            let value = match key.as_str() {
58                "repeat" => self.repeat.to_string(),
59                "repeat_current" => self.repeat_current.to_string(),
60                "shuffle" => self.shuffle.to_string(),
61                "aaa_mode" => self.aaa_mode.to_string(),
62                "volume_left" => self.volume.left.to_string(),
63                "volume_right" => self.volume.right.to_string(),
64                "volume" => {
65                    if self.volume.left == self.volume.right {
66                        self.volume.left.to_string()
67                    } else {
68                        format!("{}:{}", self.volume.left, self.volume.right)
69                    }
70                }
71                _ => "".to_string(),
72            };
73            processed = processed.replace(&format!("{{{key}}}"), &value);
74        });
75
76        #[cfg(feature = "debug")]
77        info!("Processed template: {}", processed);
78
79        processed
80    }
81}
82
83impl FromStr for AAAMode {
84    type Err = CmusError;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        match s {
88            "all" => Ok(Self::All),
89            "album" => Ok(Self::Album),
90            "artist" => Ok(Self::Artist),
91            _ => Err(CmusError::UnknownAAAMode(s.to_string())),
92        }
93    }
94}
95
96impl FromStr for Shuffle {
97    type Err = CmusError;
98
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        match s {
101            "off" => Ok(Self::Off),
102            "tracks" => Ok(Self::Tracks),
103            "albums" => Ok(Self::Albums),
104            _ => Err(CmusError::UnknownShuffleMode(s.to_string())),
105        }
106    }
107}
108
109impl FromStr for PlayerSettings {
110    type Err = CmusError;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        #[cfg(feature = "debug")]
114        info!("Parsing cmus response from string: {}", s);
115        let mut repeat = false;
116        let mut repeat_current = false;
117        let mut shuffle = Shuffle::default();
118        let mut aaa_mode = AAAMode::default();
119        let mut volume = Volume::default();
120
121        for line in s.lines() {
122            #[cfg(feature = "debug")]
123            debug!("Parsing line: {}", line);
124            if line.starts_with("set ") {
125                let line = &line[4..];
126                let (key, value) = line.split_once(' ').ok_or(CmusError::UnknownError(
127                    "Corrupted cmus response".to_string(),
128                ))?;
129
130                match key {
131                    "repeat" => repeat = value == "true",
132                    "repeat_current" => repeat_current = value == "true",
133                    "shuffle" => shuffle = Shuffle::from_str(value)?,
134                    "aaa_mode" => aaa_mode = AAAMode::from_str(value)?,
135                    "vol_left" => {
136                        volume.left = value
137                            .parse()
138                            .map_err(|e: ParseIntError| CmusError::UnknownError(e.to_string()))?
139                    }
140                    "vol_right" => {
141                        volume.right = value
142                            .parse()
143                            .map_err(|e: ParseIntError| CmusError::UnknownError(e.to_string()))?
144                    }
145                    _ => {}
146                }
147            }
148        }
149
150        Ok(Self {
151            repeat,
152            repeat_current,
153            shuffle,
154            aaa_mode,
155            volume,
156        })
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_parse_aaamode_from_str() {
166        let all = AAAMode::from_str("all");
167        let album = AAAMode::from_str("album");
168        let artist = AAAMode::from_str("artist");
169        let unknown = AAAMode::from_str("unknown");
170
171        assert_eq!(all, Ok(AAAMode::All));
172        assert_eq!(album, Ok(AAAMode::Album));
173        assert_eq!(artist, Ok(AAAMode::Artist));
174        assert_eq!(
175            unknown,
176            Err(CmusError::UnknownAAAMode("unknown".to_string()))
177        );
178    }
179
180    #[test]
181    fn test_parse_shuffle_mode_from_str() {
182        let off = Shuffle::from_str("off");
183        let tracks = Shuffle::from_str("tracks");
184        let albums = Shuffle::from_str("albums");
185        let unknown = Shuffle::from_str("unknown");
186
187        assert_eq!(off, Ok(Shuffle::Off));
188        assert_eq!(tracks, Ok(Shuffle::Tracks));
189        assert_eq!(albums, Ok(Shuffle::Albums));
190        assert_eq!(
191            unknown,
192            Err(CmusError::UnknownShuffleMode("unknown".to_string()))
193        );
194    }
195
196    #[test]
197    fn test_parse_player_settings_from_str() {
198        let setting_sample = include_str!(
199            "../../tests/samples/player_settings_mode-artist_vol-46_repeat-false_repeat_current-false_shuffle-tracks.txt");
200
201        let settings = PlayerSettings::from_str(setting_sample);
202
203        assert_eq!(
204            settings,
205            Ok(PlayerSettings {
206                repeat: false,
207                repeat_current: false,
208                shuffle: Shuffle::Tracks,
209                aaa_mode: AAAMode::Artist,
210                volume: Volume {
211                    left: 46,
212                    right: 46,
213                },
214            })
215        );
216    }
217}