1
use std::collections::HashMap;
2
use std::str::FromStr;
3

            
4
use serde::{Deserialize, Serialize};
5

            
6
use crate::{
7
    commands::{CommandResponse, ResponseParserError, empty_command_request},
8
    response_tokenizer::{
9
        ResponseAttributes, get_and_parse_optional_property, get_and_parse_property,
10
        get_optional_property, get_property,
11
    },
12
    types::{Audio, BoolOrOneshot, SongId, SongPosition},
13
};
14

            
15
empty_command_request!(Status, "status");
16

            
17
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18
pub enum StatusResponseState {
19
    Play,
20
    Stop,
21
    Pause,
22
}
23

            
24
impl FromStr for StatusResponseState {
25
    type Err = ();
26

            
27
1
    fn from_str(s: &str) -> Result<Self, Self::Err> {
28
1
        match s {
29
1
            "play" => Ok(StatusResponseState::Play),
30
            "stop" => Ok(StatusResponseState::Stop),
31
            "pause" => Ok(StatusResponseState::Pause),
32
            _ => Err(()),
33
        }
34
1
    }
35
}
36

            
37
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38
pub struct StatusResponse {
39
    pub partition: String,
40
    // Note: the Option<>::None here is serialized as -1
41
    pub volume: Option<u8>,
42
    pub repeat: bool,
43
    pub random: bool,
44
    pub single: BoolOrOneshot,
45
    pub consume: BoolOrOneshot,
46
    pub playlist: u32,
47
    pub playlist_length: u64,
48
    pub state: StatusResponseState,
49
    pub song: Option<SongPosition>,
50
    pub song_id: Option<SongId>,
51
    pub next_song: Option<SongPosition>,
52
    pub next_song_id: Option<SongId>,
53
    pub time: Option<(u64, u64)>,
54
    pub elapsed: Option<f64>,
55
    pub duration: Option<f64>,
56
    pub bitrate: Option<u32>,
57
    pub xfade: Option<u32>,
58
    pub mixrampdb: Option<f64>,
59
    pub mixrampdelay: Option<f64>,
60
    pub audio: Option<Audio>,
61
    pub updating_db: Option<u64>,
62
    pub error: Option<String>,
63
    pub last_loaded_playlist: Option<String>,
64
}
65

            
66
impl StatusResponse {
67
    pub fn new(
68
        partition: String,
69
        volume: Option<u8>,
70
        repeat: bool,
71
        random: bool,
72
        single: BoolOrOneshot,
73
        consume: BoolOrOneshot,
74
        playlist: u32,
75
        playlist_length: u64,
76
        state: StatusResponseState,
77
        song: Option<SongPosition>,
78
        song_id: Option<SongId>,
79
        next_song: Option<SongPosition>,
80
        next_song_id: Option<SongId>,
81
        time: Option<(u64, u64)>,
82
        elapsed: Option<f64>,
83
        duration: Option<f64>,
84
        bitrate: Option<u32>,
85
        xfade: Option<u32>,
86
        mixrampdb: Option<f64>,
87
        mixrampdelay: Option<f64>,
88
        audio: Option<Audio>,
89
        updating_db: Option<u64>,
90
        error: Option<String>,
91
        last_loaded_playlist: Option<String>,
92
    ) -> Self {
93
        Self {
94
            partition,
95
            volume,
96
            repeat,
97
            random,
98
            single,
99
            consume,
100
            playlist,
101
            playlist_length,
102
            state,
103
            song,
104
            song_id,
105
            next_song,
106
            next_song_id,
107
            time,
108
            elapsed,
109
            duration,
110
            bitrate,
111
            xfade,
112
            mixrampdb,
113
            mixrampdelay,
114
            audio,
115
            updating_db,
116
            error,
117
            last_loaded_playlist,
118
        }
119
    }
120
}
121

            
122
impl CommandResponse for StatusResponse {
123
    type Request = StatusRequest;
124

            
125
1
    fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError> {
126
1
        let parts: HashMap<_, _> = parts.into_map()?;
127
1
        let partition = get_property!(parts, "partition", Text).to_string();
128

            
129
1
        let volume = match get_property!(parts, "volume", Text) {
130
1
            "-1" => None,
131
1
            volume => Some(volume.parse().map_err(|_| {
132
                ResponseParserError::InvalidProperty("volume".to_string(), volume.to_string())
133
            })?),
134
        };
135

            
136
1
        let repeat = match get_property!(parts, "repeat", Text) {
137
1
            "0" => Ok(false),
138
1
            "1" => Ok(true),
139
            repeat => Err(ResponseParserError::InvalidProperty(
140
                "repeat".to_string(),
141
                repeat.to_string(),
142
            )),
143
        }?;
144

            
145
1
        let random = match get_property!(parts, "random", Text) {
146
1
            "0" => Ok(false),
147
1
            "1" => Ok(true),
148
            random => Err(ResponseParserError::InvalidProperty(
149
                "random".to_string(),
150
                random.to_string(),
151
            )),
152
        }?;
153

            
154
1
        let single = get_and_parse_property!(parts, "single", Text);
155
1
        let consume = get_and_parse_property!(parts, "consume", Text);
156
1
        let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
157
1
        let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
158
1
        let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
159
1
        let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
160
1
        let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
161
1
        let next_song: Option<SongPosition> =
162
1
            get_and_parse_optional_property!(parts, "nextsong", Text);
163
1
        let next_song_id: Option<SongId> =
164
1
            get_and_parse_optional_property!(parts, "nextsongid", Text);
165

            
166
1
        let time = match get_optional_property!(parts, "time", Text) {
167
1
            Some(time) => {
168
1
                let mut parts = time.split(':');
169
1
                let elapsed = parts
170
1
                    .next()
171
1
                    .ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
172
1
                    .parse()
173
1
                    .map_err(|_| {
174
                        ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
175
                    })?;
176
1
                let duration = parts
177
1
                    .next()
178
1
                    .ok_or(ResponseParserError::SyntaxError(0, time.to_string()))?
179
1
                    .parse()
180
1
                    .map_err(|_| {
181
                        ResponseParserError::InvalidProperty("time".to_string(), time.to_string())
182
                    })?;
183
1
                Some((elapsed, duration))
184
            }
185
            None => None,
186
        };
187

            
188
1
        let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
189
1
        let duration = get_and_parse_optional_property!(parts, "duration", Text);
190
1
        let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
191
1
        let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
192
1
        let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
193
1
        let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
194
1
        let audio = get_and_parse_optional_property!(parts, "audio", Text);
195
1
        let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
196
1
        let error = get_and_parse_optional_property!(parts, "error", Text);
197
1
        let last_loaded_playlist =
198
1
            get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
199

            
200
1
        Ok(StatusResponse {
201
1
            partition,
202
1
            volume,
203
1
            repeat,
204
1
            random,
205
1
            single,
206
1
            consume,
207
1
            playlist,
208
1
            playlist_length,
209
1
            state,
210
1
            song,
211
1
            song_id,
212
1
            next_song,
213
1
            next_song_id,
214
1
            time,
215
1
            elapsed,
216
1
            duration,
217
1
            bitrate,
218
1
            xfade,
219
1
            mixrampdb,
220
1
            mixrampdelay,
221
1
            audio,
222
1
            updating_db,
223
1
            error,
224
1
            last_loaded_playlist,
225
1
        })
226
1
    }
227

            
228
    fn serialize(&self) -> Vec<u8> {
229
        unimplemented!("response serialization is not yet implemented for StatusResponse")
230
    }
231
}
232

            
233
#[cfg(test)]
234
mod tests {
235
    use super::*;
236
    use indoc::indoc;
237
    use pretty_assertions::assert_eq;
238

            
239
    #[test]
240
1
    fn test_parse_status_response() {
241
1
        let contents = indoc! { r#"
242
1
            volume: 66
243
1
            repeat: 1
244
1
            random: 1
245
1
            single: 0
246
1
            consume: 0
247
1
            partition: default
248
1
            playlist: 2
249
1
            playlistlength: 78
250
1
            mixrampdb: 0
251
1
            state: play
252
1
            song: 0
253
1
            songid: 1
254
1
            time: 225:263
255
1
            elapsed: 225.376
256
1
            bitrate: 127
257
1
            duration: 262.525
258
1
            audio: 44100:f:2
259
1
            nextsong: 44
260
1
            nextsongid: 45
261
1
            OK
262
1
        "# };
263

            
264
1
        assert_eq!(
265
1
            StatusResponse::parse_raw(contents.as_bytes()),
266
1
            Ok(StatusResponse {
267
1
                partition: "default".into(),
268
1
                volume: Some(66),
269
1
                repeat: true,
270
1
                random: true,
271
1
                single: BoolOrOneshot::False,
272
1
                consume: BoolOrOneshot::False,
273
1
                playlist: 2,
274
1
                playlist_length: 78,
275
1
                state: StatusResponseState::Play,
276
1
                song: Some(0),
277
1
                song_id: Some(1),
278
1
                next_song: Some(44),
279
1
                next_song_id: Some(45),
280
1
                time: Some((225, 263)),
281
1
                elapsed: Some(225.376),
282
1
                duration: Some(262.525),
283
1
                bitrate: Some(127),
284
1
                xfade: None,
285
1
                mixrampdb: Some(0.0),
286
1
                mixrampdelay: None,
287
1
                audio: Some(Audio {
288
1
                    sample_rate: 44100,
289
1
                    bits: 16,
290
1
                    channels: 2,
291
1
                }),
292
1
                updating_db: None,
293
1
                error: None,
294
1
                last_loaded_playlist: None,
295
1
            }),
296
        );
297
1
    }
298
}