cmus_notify/cmus/
query.rs1use crate::cmus::events::CmusEvent;
2use crate::cmus::player_settings::PlayerSettings;
3use crate::cmus::{CmusError, Track};
4#[cfg(feature = "debug")]
5use log::{debug, info};
6use std::str::FromStr;
7
8#[derive(PartialEq, Default)]
12#[cfg_attr(feature = "debug", derive(Debug))]
13pub struct CmusQueryResponse {
14 track_row: String,
15 player_settings_row: String,
16}
17
18impl FromStr for CmusQueryResponse {
19 type Err = String;
20
21 #[inline]
22 fn from_str(s: &str) -> Result<Self, Self::Err> {
23 #[cfg(feature = "debug")]
24 info!("Parsing cmus response from string: {}", s);
25
26 let sep_index = s.find("set ").ok_or("Corrupted cmus response")?;
27
28 Ok(Self {
29 track_row: s[..sep_index].to_string(),
30 player_settings_row: s[sep_index..].to_string(),
31 })
32 }
33}
34
35impl CmusQueryResponse {
36 #[inline(always)]
38 pub fn track(&self) -> Result<Track, CmusError> {
39 Track::from_str(&self.track_row)
40 }
41
42 #[inline(always)]
44 pub fn player_settings(&self) -> Result<PlayerSettings, CmusError> {
45 PlayerSettings::from_str(&self.player_settings_row)
46 }
47
48 pub fn events(&self, other: &Self) -> Result<Vec<CmusEvent>, CmusError> {
50 #[cfg(feature = "debug")]
51 info!("Comparing cmus responses: {:?} and {:?}", self, other);
52
53 if self.track_row.is_empty() || self.player_settings_row.is_empty() {
54 #[cfg(feature = "debug")]
55 info!("Cmus response is empty, returning empty events");
56 return Ok(Vec::new());
57 }
58
59 let mut events = Vec::new();
60
61 let track = self.track()?;
62 let other_track = other.track()?;
63
64 let other_player_settings = other.player_settings()?;
65
66 if track != other_track {
67 #[cfg(feature = "debug")]
68 debug!("Track changed: {:?} -> {:?}", other_track, track);
69
70 if track.path != other_track.path {
71 #[cfg(feature = "debug")]
72 debug!("Track changed: {:?} -> {:?}", other_track, track);
73 events.push(CmusEvent::TrackChanged(
74 other_track,
75 other_player_settings,
76 ));
77 return Ok(events);
79 } else if track.status != other_track.status {
80 #[cfg(feature = "debug")]
81 debug!(
82 "Status changed: {:?} -> {:?}",
83 other_track.status, track.status
84 );
85 events.push(CmusEvent::StatusChanged(
86 other_track.clone(),
87 other_player_settings.clone(),
88 ));
89 } else if track.position != other_track.position {
90 #[cfg(feature = "debug")]
91 debug!(
92 "Position changed: {:?} -> {:?}",
93 other_track.position, track.position
94 );
95 events.push(CmusEvent::PositionChanged(
96 track,
97 other_player_settings.clone(),
98 ));
99 }
100 }
101
102 let player_settings = self.player_settings()?;
103
104 if player_settings != other_player_settings {
105 #[cfg(feature = "debug")]
106 debug!(
107 "Player settings changed: {:?} -> {:?}",
108 other_player_settings, player_settings
109 );
110
111 if player_settings.shuffle != other_player_settings.shuffle {
112 #[cfg(feature = "debug")]
113 debug!(
114 "Shuffle changed: {:?} -> {:?}",
115 other_player_settings.shuffle, player_settings.shuffle
116 );
117
118 events.push(CmusEvent::ShuffleChanged(
119 other_track.clone(),
120 other_player_settings.clone(),
121 ));
122 }
123
124 if player_settings.repeat != other_player_settings.repeat {
125 #[cfg(feature = "debug")]
126 debug!(
127 "Repeat changed: {:?} -> {:?}",
128 other_player_settings.repeat, player_settings.repeat
129 );
130
131 events.push(CmusEvent::RepeatChanged(
132 other_track.clone(),
133 other_player_settings.clone(),
134 ));
135 }
136
137 if player_settings.aaa_mode != other_player_settings.aaa_mode {
138 #[cfg(feature = "debug")]
139 debug!(
140 "AAA mode changed: {:?} -> {:?}",
141 other_player_settings.aaa_mode, player_settings.aaa_mode
142 );
143
144 events.push(CmusEvent::AAAModeChanged(
145 other_track.clone(),
146 other_player_settings.clone(),
147 ));
148 }
149
150 if player_settings.volume != other_player_settings.volume {
151 #[cfg(feature = "debug")]
152 debug!(
153 "Volume changed: {:?} -> {:?}",
154 other_player_settings.volume, player_settings.volume
155 );
156
157 events.push(CmusEvent::VolumeChanged(other_track, other_player_settings));
158 }
159 }
160
161 #[cfg(feature = "debug")]
162 info!("Returning events: {:?}", events);
163
164 Ok(events)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::cmus::player_settings::{AAAMode, Shuffle};
172 use crate::cmus::TrackStatus;
173 use test_context::{test_context, TestContext};
174
175 #[test]
176 fn test_parse_query_from_str() {
177 let row = include_str!("../../tests/samples/row/cmus-remote-output-row.txt");
178 let query = CmusQueryResponse::from_str(row);
179
180 assert!(query.is_ok());
181 let query = query.unwrap();
182
183 assert_eq!(
184 query.track_row,
185 include_str!("../../tests/samples/row/cmus-remote-output-track-row.txt")
186 );
187 assert_eq!(
188 query.player_settings_row,
189 include_str!("../../tests/samples/row/cmus-remote-output-player-row.txt")
190 );
191 }
192
193 struct Context {
194 query: CmusQueryResponse,
195 }
196
197 impl TestContext for Context {
198 fn setup() -> Self {
199 let row = include_str!("../../tests/samples/row/cmus-remote-output-row.txt");
200 let query = CmusQueryResponse::from_str(row).unwrap();
201
202 Self { query }
203 }
204 }
205
206 #[test_context(Context)]
207 #[test]
208 fn test_actually_parse_the_track_info(ctx: &Context) {
209 let track = ctx.query.track();
210
211 assert!(track.is_ok());
212 let track = track.unwrap();
213
214 assert_eq!(
215 track.path,
216 "/mnt/Data/Music/FLAC/Taylor Swift/Taylor Swift - Speak Now/12 - Haunted.mp3"
217 );
218 assert_eq!(track.status, TrackStatus::Playing);
219 assert_eq!(track.position, 34);
220 assert_eq!(track.duration, 242);
221 let metadata = track.metadata;
222 assert_eq!(metadata.get("artist"), Some("Taylor Swift"));
223 assert_eq!(metadata.get("album"), Some("Speak Now"));
224 assert_eq!(metadata.get("title"), Some("Haunted"));
225 assert_eq!(metadata.get("date"), Some("2010"));
226 assert_eq!(metadata.get("genre"), Some("Pop"));
227 assert_eq!(metadata.get("discnumber"), Some("1"));
228 assert_eq!(metadata.get("tracknumber"), Some("12"));
229 assert_eq!(metadata.get("albumartist"), Some("Taylor Swift"));
230 assert_eq!(metadata.get("replaygain_track_gain"), Some("-11.3 dB"));
231 assert_eq!(metadata.get("composer"), Some("Taylor Swift"));
232 assert_eq!(metadata.get("label"), Some("Big Machine Records, LLC"));
233 assert_eq!(metadata.get("publisher"), Some("Big Machine Records, LLC"));
234 assert_eq!(metadata.get("bpm"), Some("162"));
235 assert_eq!(metadata.get("comment"), None);
236 }
237
238 #[test_context(Context)]
239 #[test]
240 fn test_actually_parse_the_player_settings(ctx: &Context) {
241 let player_settings = ctx.query.player_settings();
242
243 assert!(player_settings.is_ok());
244 let player_settings = player_settings.unwrap();
245
246 assert_eq!(player_settings.aaa_mode, AAAMode::All);
247 assert!(player_settings.repeat);
248 assert!(!player_settings.repeat_current);
249 assert_eq!(player_settings.shuffle, Shuffle::Off);
250 assert_eq!(player_settings.volume.left, 17);
251 assert_eq!(player_settings.volume.right, 17);
252 }
253}