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

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

            
6
use crate::common::{Audio, BoolOrOneshot, SongId, SongPosition};
7

            
8
use crate::commands::{
9
    get_and_parse_optional_property, get_and_parse_property, get_optional_property, get_property,
10
    Command, GenericResponseValue, Request, RequestParserResult, ResponseAttributes,
11
    ResponseParserError,
12
};
13

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

            
21
impl FromStr for StatusResponseState {
22
    type Err = ();
23

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

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

            
63
#[inline]
64
1
fn parse_status_response(
65
1
    parts: ResponseAttributes<'_>,
66
1
) -> Result<StatusResponse, ResponseParserError> {
67
1
    let parts: HashMap<&str, GenericResponseValue> = parts.into();
68
1
    let partition = get_property!(parts, "partition", Text).to_string();
69

            
70
1
    let volume = match get_property!(parts, "volume", Text) {
71
1
        "-1" => None,
72
1
        volume => Some(
73
1
            volume
74
1
                .parse()
75
1
                .map_err(|_| ResponseParserError::InvalidProperty("volume", volume))?,
76
        ),
77
    };
78

            
79
1
    let repeat = match get_property!(parts, "repeat", Text) {
80
1
        "0" => Ok(false),
81
1
        "1" => Ok(true),
82
        repeat => Err(ResponseParserError::InvalidProperty("repeat", repeat)),
83
    }?;
84

            
85
1
    let random = match get_property!(parts, "random", Text) {
86
1
        "0" => Ok(false),
87
1
        "1" => Ok(true),
88
        random => Err(ResponseParserError::InvalidProperty("random", random)),
89
    }?;
90

            
91
1
    let single = get_and_parse_property!(parts, "single", Text);
92
1
    let consume = get_and_parse_property!(parts, "consume", Text);
93
1
    let playlist: u32 = get_and_parse_property!(parts, "playlist", Text);
94
1
    let playlist_length: u64 = get_and_parse_property!(parts, "playlistlength", Text);
95
1
    let state: StatusResponseState = get_and_parse_property!(parts, "state", Text);
96
1
    let song: Option<SongPosition> = get_and_parse_optional_property!(parts, "song", Text);
97
1
    let song_id: Option<SongId> = get_and_parse_optional_property!(parts, "songid", Text);
98
1
    let next_song: Option<SongPosition> = get_and_parse_optional_property!(parts, "nextsong", Text);
99
1
    let next_song_id: Option<SongId> = get_and_parse_optional_property!(parts, "nextsongid", Text);
100

            
101
1
    let time = match get_optional_property!(parts, "time", Text) {
102
1
        Some(time) => {
103
1
            let mut parts = time.split(':');
104
1
            let elapsed = parts
105
1
                .next()
106
1
                .ok_or(ResponseParserError::SyntaxError(0, time))?
107
1
                .parse()
108
1
                .map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
109
1
            let duration = parts
110
1
                .next()
111
1
                .ok_or(ResponseParserError::SyntaxError(0, time))?
112
1
                .parse()
113
1
                .map_err(|_| ResponseParserError::InvalidProperty("time", time))?;
114
1
            Some((elapsed, duration))
115
        }
116
        None => None,
117
    };
118

            
119
1
    let elapsed = get_and_parse_optional_property!(parts, "elapsed", Text);
120
1
    let duration = get_and_parse_optional_property!(parts, "duration", Text);
121
1
    let bitrate = get_and_parse_optional_property!(parts, "bitrate", Text);
122
1
    let xfade = get_and_parse_optional_property!(parts, "xfade", Text);
123
1
    let mixrampdb = get_and_parse_optional_property!(parts, "mixrampdb", Text);
124
1
    let mixrampdelay = get_and_parse_optional_property!(parts, "mixrampdelay", Text);
125
1
    let audio = get_and_parse_optional_property!(parts, "audio", Text);
126
1
    let updating_db = get_and_parse_optional_property!(parts, "updating_db", Text);
127
1
    let error = get_and_parse_optional_property!(parts, "error", Text);
128
1
    let last_loaded_playlist =
129
1
        get_and_parse_optional_property!(parts, "last_loaded_playlist", Text);
130

            
131
1
    Ok(StatusResponse {
132
1
        partition,
133
1
        volume,
134
1
        repeat,
135
1
        random,
136
1
        single,
137
1
        consume,
138
1
        playlist,
139
1
        playlist_length,
140
1
        state,
141
1
        song,
142
1
        song_id,
143
1
        next_song,
144
1
        next_song_id,
145
1
        time,
146
1
        elapsed,
147
1
        duration,
148
1
        bitrate,
149
1
        xfade,
150
1
        mixrampdb,
151
1
        mixrampdelay,
152
1
        audio,
153
1
        updating_db,
154
1
        error,
155
1
        last_loaded_playlist,
156
1
    })
157
1
}
158

            
159
pub struct Status;
160

            
161
impl Command for Status {
162
    type Response = StatusResponse;
163
    const COMMAND: &'static str = "status";
164

            
165
    fn parse_request(mut parts: std::str::SplitWhitespace<'_>) -> RequestParserResult<'_> {
166
        debug_assert!(parts.next().is_none());
167

            
168
        Ok((Request::Status, ""))
169
    }
170

            
171
1
    fn parse_response(
172
1
        parts: ResponseAttributes<'_>,
173
1
    ) -> Result<Self::Response, ResponseParserError> {
174
1
        parse_status_response(parts)
175
1
    }
176
}
177

            
178
#[cfg(test)]
179
mod tests {
180
    use super::*;
181
    use indoc::indoc;
182
    use pretty_assertions::assert_eq;
183

            
184
    #[test]
185
1
    fn test_parse_status_response() {
186
1
        let contents = indoc! { r#"
187
1
            volume: 66
188
1
            repeat: 1
189
1
            random: 1
190
1
            single: 0
191
1
            consume: 0
192
1
            partition: default
193
1
            playlist: 2
194
1
            playlistlength: 78
195
1
            mixrampdb: 0
196
1
            state: play
197
1
            song: 0
198
1
            songid: 1
199
1
            time: 225:263
200
1
            elapsed: 225.376
201
1
            bitrate: 127
202
1
            duration: 262.525
203
1
            audio: 44100:f:2
204
1
            nextsong: 44
205
1
            nextsongid: 45
206
1
            OK
207
1
        "# };
208
1

            
209
1
        assert_eq!(
210
1
            Status::parse_raw_response(contents),
211
1
            Ok(StatusResponse {
212
1
                partition: "default".into(),
213
1
                volume: Some(66),
214
1
                repeat: true,
215
1
                random: true,
216
1
                single: BoolOrOneshot::False,
217
1
                consume: BoolOrOneshot::False,
218
1
                playlist: 2,
219
1
                playlist_length: 78,
220
1
                state: StatusResponseState::Play,
221
1
                song: Some(0),
222
1
                song_id: Some(1),
223
1
                next_song: Some(44),
224
1
                next_song_id: Some(45),
225
1
                time: Some((225, 263)),
226
1
                elapsed: Some(225.376),
227
1
                duration: Some(262.525),
228
1
                bitrate: Some(127),
229
1
                xfade: None,
230
1
                mixrampdb: Some(0.0),
231
1
                mixrampdelay: None,
232
1
                audio: Some(Audio {
233
1
                    sample_rate: 44100,
234
1
                    bits: 16,
235
1
                    channels: 2,
236
1
                }),
237
1
                updating_db: None,
238
1
                error: None,
239
1
                last_loaded_playlist: None,
240
1
            }),
241
1
        );
242
1
    }
243
}