1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
//! The status module.
//!
//! This module contains methods to turn the JSON responses
//! from the Spotify connector into easy-to-use structures.
//!
//! It also contains some extra abstractions, such as the `SimpleTrack` struct.

use json::JsonValue;
use time::{self, Timespec, Tm};

/// A change in the Spotify status.
pub struct SpotifyStatusChange {
    /// Indicates a change in the volume.
    pub volume: bool,
    /// Indicates a change in the online status.
    pub online: bool,
    /// Indicates a change in the protocol version.
    pub version: bool,
    /// Indicates a change in the running state.
    pub running: bool,
    /// Indicates a change in the playing state.
    pub playing: bool,
    /// Indicates a change in the shuffle mode.
    pub shuffle: bool,
    /// Indicates a change in the server time.
    pub server_time: bool,
    /// Indicates a change in the play enabled state.
    pub play_enabled: bool,
    /// Indicates a change in the prev enabled state.
    pub prev_enabled: bool,
    /// Indicates a change in the next enabled state.
    pub next_enabled: bool,
    /// Indicates a change in the client version.
    pub client_version: bool,
    /// Indicates a change in the playing position.
    pub playing_position: bool,
    /// Indicates a change in the open graph data.
    pub open_graph_state: bool,
    /// Indicates a change in the track.
    pub track: bool,
}

/// A Spotify status.
#[derive(Debug, Clone, PartialEq)]
pub struct SpotifyStatus {
    /// The volume.
    /// Valid values are [0.0...1.0].
    volume: f32,
    /// Whether the client is online.
    online: bool,
    /// The protocol version.
    version: i32,
    /// Whether the client is running.
    running: bool,
    /// Whether a track is currently playing.
    playing: bool,
    /// Whether shuffle mode is activated.
    shuffle: bool,
    /// The server time as a unix timestamp.
    server_time: i64,
    /// Whether playing a track is enabled.
    play_enabled: bool,
    /// Whether playing the previous track is enabled.
    prev_enabled: bool,
    /// Whether playing the next track is enabled.
    next_enabled: bool,
    /// The client version.
    client_version: String,
    /// The current playing position.
    playing_position: f32,
    /// The Open Graph state.
    open_graph_state: OpenGraphState,
    /// The currently playing track.
    track: Track,
}

/// A Spotify Open Graph state.
#[derive(Debug, Clone, PartialEq)]
struct OpenGraphState {
    /// Whether the current session is private.
    private_session: bool,
    /// Whether posting is disabled.
    posting_disabled: bool,
}

/// A Spotify track.
#[derive(Debug, Clone, PartialEq)]
struct Track {
    /// The track.
    track: Resource,
    /// The album.
    album: Resource,
    /// The artist.
    artist: Resource,
    /// The length in full seconds.
    length: i32,
    /// The track type.
    track_type: String,
}

/// A Spotify resource.
#[derive(Debug, Clone, PartialEq)]
struct Resource {
    /// The internal resource uri.
    uri: String,
    /// The name.
    name: String,
    /// The location.
    location: ResourceLocation,
}

/// A Spotify resource location.
#[derive(Debug, Clone, PartialEq)]
struct ResourceLocation {
    /// The online resource url.
    og: String,
}

/// A simple track.
/// Provides an abstraction over the more
/// complicated and quite messy `Track` struct.
#[derive(Debug, Clone, PartialEq)]
pub struct SimpleTrack {
    /// The track name.
    pub name: String,
    /// The album name.
    pub album: String,
    /// The artist name.
    pub artist: String,
}

/// Transforms a JSON value into an owned String.
#[inline]
fn get_json_str(json: &JsonValue) -> String {
    match json.as_str() {
        Some(val) => val.to_owned(),
        None => String::default(),
    }
}

/// Implements `SpotifyStatus`.
impl SpotifyStatus {
    /// Gets an easy-to-work-with abstraction over
    /// the currently playing track, containing only
    /// the names of the track, album and artist.
    pub fn track(&self) -> SimpleTrack {
        SimpleTrack::from(&self.track)
    }
    /// Gets the client version.
    pub fn version(&self) -> String {
        self.client_version.clone()
    }
    /// Gets the volume.
    /// Possible values range from `0.0_f32` to `1.0_f32`.
    pub fn volume(&self) -> f32 {
        self.volume
    }
    /// Gets the volume as percentage.
    /// Possible values range from `0.0_f32` to `100.0_f32`.
    pub fn volume_percentage(&self) -> f32 {
        (self.volume * 100_f32).trunc()
    }
    /// Gets the server timestamp.
    pub fn timestamp(&self) -> i64 {
        self.server_time
    }
    /// Gets the local server time.
    pub fn time(&self) -> Tm {
        time::at(Timespec::new(self.server_time, 0))
    }
    /// Gets the coordinated universal server time.
    pub fn time_utc(&self) -> Tm {
        time::at_utc(Timespec::new(self.server_time, 0))
    }
    /// Gets a value indicating whether shuffling is enabled.
    pub fn shuffle_enabled(&self) -> bool {
        self.shuffle
    }
    /// Gets a value indicating whether the client is
    /// currently connected to the Internet.
    pub fn is_online(&self) -> bool {
        self.online
    }
    /// Gets a value indicating whether the current
    /// session is a private session.
    pub fn is_private_session(&self) -> bool {
        self.open_graph_state.private_session
    }
}

/// Implements `SpotifyStatusChange`.
impl SpotifyStatusChange {
    /// Constructs a new `SpotifyStatusChange` with all fields set to true.
    pub fn new_true() -> SpotifyStatusChange {
        SpotifyStatusChange {
            volume: true,
            online: true,
            version: true,
            running: true,
            playing: true,
            shuffle: true,
            server_time: true,
            play_enabled: true,
            prev_enabled: true,
            next_enabled: true,
            client_version: true,
            playing_position: true,
            open_graph_state: true,
            track: true,
        }
    }
}

/// Implements `From<JsonValue>` for `SpotifyStatus`.
impl From<JsonValue> for SpotifyStatus {
    fn from(json: JsonValue) -> SpotifyStatus {
        SpotifyStatus {
            volume: json["volume"].as_f32().unwrap_or(0_f32),
            online: json["online"] == true,
            version: json["version"].as_i32().unwrap_or(0_i32),
            running: json["running"] == true,
            playing: json["playing"] == true,
            shuffle: json["shuffle"] == true,
            server_time: json["server_time"].as_i64().unwrap_or(0_i64),
            play_enabled: json["play_enabled"] == true,
            prev_enabled: json["prev_enabled"] == true,
            next_enabled: json["next_enabled"] == true,
            client_version: get_json_str(&json["client_version"]),
            playing_position: json["playing_position"].as_f32().unwrap_or(0_f32),
            open_graph_state: OpenGraphState::from(&json["open_graph_state"]),
            track: Track::from(&json["track"]),
        }
    }
}

/// Implements `From<&'a JsonValue>` for `OpenGraphState`.
impl<'a> From<&'a JsonValue> for OpenGraphState {
    fn from(json: &'a JsonValue) -> OpenGraphState {
        OpenGraphState {
            private_session: json["private_session"] == true,
            posting_disabled: json["posting_disabled"] == true,
        }
    }
}

/// Implements `From<&'a JsonValue>` for `Track`.
impl<'a> From<&'a JsonValue> for Track {
    fn from(json: &'a JsonValue) -> Track {
        Track {
            track_type: get_json_str(&json["uri"]),
            track: Resource::from(&json["track_resource"]),
            album: Resource::from(&json["album_resource"]),
            artist: Resource::from(&json["artist_resource"]),
            length: json["length"].as_i32().unwrap_or(0_i32),
        }
    }
}

/// Implements `From<&'a JsonValue>` for `Resource`.
impl<'a> From<&'a JsonValue> for Resource {
    fn from(json: &'a JsonValue) -> Resource {
        Resource {
            uri: get_json_str(&json["uri"]),
            name: get_json_str(&json["name"]),
            location: ResourceLocation::from(&json["location"]),
        }
    }
}

/// Implements `From<&'a JsonValue>` for `ResourceLocation`.
impl<'a> From<&'a JsonValue> for ResourceLocation {
    fn from(json: &'a JsonValue) -> ResourceLocation {
        ResourceLocation { og: get_json_str(&json["og"]) }
    }
}

/// Implements `From<Track>` for `SimpleTrack`.
impl<'a> From<&'a Track> for SimpleTrack {
    fn from(track: &'a Track) -> SimpleTrack {
        SimpleTrack {
            name: track.track.name.clone(),
            album: track.album.name.clone(),
            artist: track.artist.name.clone(),
        }
    }
}

/// Implements `From<SpotifyStatus>` for `SimpleTrack`.
impl<'a> From<&'a SpotifyStatus> for SimpleTrack {
    fn from(status: &'a SpotifyStatus) -> SimpleTrack {
        SimpleTrack::from(&status.track)
    }
}

/// Implements `fmt::Display` for `SimpleTrack`.
impl ::std::fmt::Display for SimpleTrack {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        write!(f, "{} - {}", self.artist, self.name)
    }
}

/// Implements `From<(SpotifyStatus, SpotifyStatus)>` for `SpotifyStatusChange`.
impl From<(SpotifyStatus, SpotifyStatus)> for SpotifyStatusChange {
    fn from(set: (SpotifyStatus, SpotifyStatus)) -> SpotifyStatusChange {
        let curr = set.0;
        let last = set.1;
        macro_rules! status_compare_field {
            ($field:ident) => (curr.$field != last.$field)
        }
        SpotifyStatusChange {
            volume: status_compare_field!(volume),
            online: status_compare_field!(online),
            version: status_compare_field!(version),
            running: status_compare_field!(running),
            playing: status_compare_field!(playing),
            shuffle: status_compare_field!(shuffle),
            server_time: status_compare_field!(server_time),
            play_enabled: status_compare_field!(play_enabled),
            prev_enabled: status_compare_field!(prev_enabled),
            next_enabled: status_compare_field!(next_enabled),
            client_version: status_compare_field!(client_version),
            playing_position: status_compare_field!(playing_position),
            open_graph_state: status_compare_field!(open_graph_state),
            track: status_compare_field!(track),
        }
    }
}