summary history branches tags files
src/webapi/mod.rs
#[cfg(test)]
mod test;

extern crate time;
extern crate timer;

extern crate chrono;
use self::chrono::{DateTime, Local, Datelike, Timelike, Weekday};

use std::fmt;
use std::iter;
use std::iter::Iterator;
use std::collections::BTreeMap;
use std::sync::mpsc::{channel, Receiver};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::str::FromStr;

extern crate serde_json;
use self::serde_json::Value;

use super::{Scrobbler, Scrobble};

use super::http;
use super::settings;
use super::SpotifyEndpoints;
use super::SPOTIFY_API;
use super::http::HttpResponse;

pub type DeviceId = String;
pub type SpotifyResponse = HttpResponse;

pub fn parse_spotify_token(json: &str) -> Option<(String, String, u64)> {
    if let Ok(json_data) = serde_json::from_str(json) {
        let json_data: Value = json_data;
        let access_token = json_data["access_token"].as_str().unwrap_or("");
        let refresh_token = match json_data.get("refresh_token") {
            Some(j) => j.as_str().unwrap(),
            None => "",
        };
        let expires_in = json_data["expires_in"].as_u64().unwrap_or(0 as u64);
        return Some((String::from(access_token),String::from(refresh_token), expires_in));
    }
    None
}

#[derive(Deserialize, Debug, Default)]
pub struct ConnectDevice {
    pub id: Option<String>,
    pub is_active: bool,
    pub is_restricted: bool,
    pub name: String,
    #[serde(rename(deserialize = "type"))]
    pub device_type: String,
    pub volume_percent: Option<u32>
}

impl fmt::Display for ConnectDevice {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let id = match self.id {
            Some(ref id) => id,
            None => "",
        };
        write!(f, "{:<40} <{}>", self.name, id)
    }
}

#[derive(Deserialize, Default)]
pub struct ConnectDeviceList {
    pub devices: Vec<ConnectDevice>,
}

impl fmt::Display for ConnectDeviceList {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for dev in &self.devices {
            let _ = write!(f, "{}\n", dev);
        }
        Ok(())
    }
}

impl<'a> iter::IntoIterator for &'a ConnectDeviceList {
    type Item = &'a ConnectDevice;
    //type IntoIter = ::std::vec::IntoIter<ConnectDevice>;
    type IntoIter = ::std::slice::Iter<'a, ConnectDevice>;
    fn into_iter(self) -> Self::IntoIter {
        (&self.devices).into_iter()
    }
}

impl iter::IntoIterator for ConnectDeviceList {
    type Item = ConnectDevice;
    type IntoIter = ::std::vec::IntoIter<ConnectDevice>;
    fn into_iter(self) -> Self::IntoIter {
        self.devices.into_iter()
    }
}

impl ConnectDeviceList {
    pub fn len(&self) -> usize {
        self.devices.len()
    }
}

#[derive(Deserialize, Debug)]
pub struct ConnectPlaybackArtist {
    pub name: String,
    pub uri: String,
}

#[derive(Deserialize, Debug)]
pub struct ConnectPlaybackAlbum {
    pub name: String,
    pub uri: String,
}

#[derive(Deserialize, Debug)]
pub struct ConnectPlaybackItem {
    pub duration_ms: u64,
    pub name: String,
    pub uri: String,
    pub album: ConnectPlaybackAlbum,
    pub artists: Vec<ConnectPlaybackArtist>,
}

#[derive(Deserialize, Debug)]
pub struct ConnectContext {
    pub uri: String,
}

#[derive(Deserialize, Debug, Default)]
pub struct PlayerState {
    pub timestamp: i64,
    pub device: ConnectDevice,
    pub progress_ms: Option<u64>,
    pub is_playing: bool,
    pub item: Option<ConnectPlaybackItem>,
    pub shuffle_state: bool,
    pub repeat_state: String,
    pub context: Option<ConnectContext>,
}

impl PlayerState {
    pub fn playing_from_context(&self, context: &str) -> bool {
        match self.context {
            Some(ref ctx) => ctx.uri == context,
            None => false,
        }
    }
}

impl fmt::Display for PlayerState {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let play_state = match self.is_playing {
            true => "Playing",
            false => "Paused",
        };
        let volume = match self.device.volume_percent {
            Some(x) => x.to_string(),
            None => "???".to_string(),
        };
        let position: f64 = match self.progress_ms {
            Some(x) => (x as f64)/1000.0,
            None => 0.0,
        };
        if let Some(ref item) = self.item {
            let duration: f64 = (item.duration_ms as f64) / 1000.0;
            let progress: f64 = position/duration*100.0;
            write!(f, "{} on {} [Volume {}%]\n{} <{}>\n{}s / {}s ({:.1}%)\n",
                   play_state, self.device.name, volume,
                   &item.name, &item.uri,
                   position, duration, progress)
        }
        else {
            write!(f, "{} on {} [Volume {}%]\n{} <{}>\n{}s / {}s ({:.1}%)\n",
                   play_state, self.device.name, volume,
                   "unknown", "unknown",
                   position, 0, 0)
        }
    }
}

#[derive(Serialize)]
pub struct PlayContextOffset {
    pub position: Option<u32>,
    pub uri: Option<String>,
}
impl Default for PlayContextOffset {
    fn default() -> PlayContextOffset { PlayContextOffset { position: None, uri: None } }
}
impl Clone for PlayContextOffset {
    fn clone(&self) -> PlayContextOffset {
        PlayContextOffset {
            position: self.position.clone(),
            uri: self.uri.clone(),
        }
    }
}

#[derive(Serialize)]
pub struct PlayContext {
    pub context_uri: Option<String>,
    pub uris: Option<Vec<String>>,
    pub offset: Option<PlayContextOffset>,
}
impl Default for PlayContext {
    fn default() -> PlayContext { PlayContext { context_uri: None, uris: None, offset: None } }
}
impl PlayContext {
    pub fn new() -> PlayContext {
        PlayContext::default()
    }
    pub fn context_uri<'a>(&'a mut self, uri: &str) -> &'a mut PlayContext {
        self.context_uri = Some(uri.to_string());
        self
    }
    pub fn uri<'a>(&'a mut self, uri: &str) -> &'a mut PlayContext {
        match self.uris {
            Some(ref mut uris) => uris.push(uri.to_string()),
            None => {
                let mut vec = Vec::<String>::new();
                vec.push(uri.to_string());
                self.uris = Some(vec);
            },
        };
        self
    }
    pub fn offset_position<'a>(&'a mut self, position: u32) -> &'a mut PlayContext {
        match self.offset {
            Some(ref mut o) => o.position = Some(position),
            None => {
                let mut o = PlayContextOffset::default();
                o.position = Some(position);
                self.offset = Some(o);
            }
        };
        self
    }
    pub fn offset_uri<'a>(&'a mut self, uri: &str) -> &'a mut PlayContext {
        match self.offset {
            Some(ref mut o) => o.uri = Some(uri.to_string()),
            None => {
                let mut o = PlayContextOffset::default();
                o.uri = Some(uri.to_string());
                self.offset = Some(o);
            }
        };
        self
    }
    pub fn build(&self) -> PlayContext {
        PlayContext { context_uri: self.context_uri.clone(),
                      uris: self.uris.clone(),
                      offset: self.offset.clone() }
    }
}

struct QueryString {
    map: BTreeMap<String,String>,
}
impl QueryString {
    fn new() -> QueryString { QueryString { map: BTreeMap::<String,String>::new() } }
    fn add_opt(&mut self, key: &str, value: Option<String>) -> &mut QueryString {
        match value {
            Some(v) => { self.map.insert(key.to_string(), v); },
            None => {},
        }
        self
    }
    fn add<A>(&mut self, key: &str, value: A) -> &mut QueryString
        where A: ToString {
        self.map.insert(key.to_string(), value.to_string());
        self
    }
    fn build(&self) -> String {
        let mut s = String::new();
        for (key, val) in &self.map {
            match s.len() {
                0 => { } // '?' inserted in HTTP layer
                _ => { s = s + "&"; }
            }
            s = s + &format!("{}={}", key, val);
        }
        s
    }
}

#[derive(PartialEq, Debug, Clone, Copy)]
pub enum AlarmRepeat {
    Daily,
    Weekdays,
    Weekends,
}
impl Default for AlarmRepeat {
    fn default() -> Self { AlarmRepeat::Daily }
}
impl fmt::Display for AlarmRepeat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            &AlarmRepeat::Daily => "daily",
            &AlarmRepeat::Weekdays => "weekdays",
            &AlarmRepeat::Weekends => "weekends",
        };
        write!(f, "{}", s)
    }
}
impl FromStr for AlarmRepeat {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.trim() {
            "weekdays" => Ok(AlarmRepeat::Weekdays),
            "weekends" => Ok(AlarmRepeat::Weekends),
            _ => Ok(AlarmRepeat::Daily),
        }
    }
}

pub struct AlarmEntry {
    pub time: String,
    pub repeat: AlarmRepeat,
    pub context: PlayContext,
    pub device: DeviceId,
    #[cfg(test)]
    pub now: Option<DateTime<Local>>,
}

impl<'a> From<&'a AlarmConfig> for AlarmEntry {
    fn from(alarm: &AlarmConfig) -> Self {
        AlarmEntry {
            time: format!("{:02}:{:02}", alarm.hour, alarm.minute),
            repeat: alarm.repeat,
            context: PlayContext::new()
                .context_uri(&alarm.context)
                .offset_position(0)
                .build(),
            device: alarm.device.clone(),
            #[cfg(test)]
            now: None,
        }
    }
}

#[derive(Default, Debug, Clone)]
pub struct AlarmConfig {
    pub hour: u32,
    pub minute: u32,
    pub context: String,
    pub repeat: AlarmRepeat,
    pub device: String,
}
impl FromStr for AlarmConfig {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut fields = s.split(",");
        let time = fields.next().ok_or("Missing time")?;
        let repeat = fields.next().ok_or("Missing repeat")?;
        let context = fields.next().ok_or("Missing context")?;
        let device = fields.next().ok_or("Missing device")?;
        if device.is_empty() || context.is_empty() || time.is_empty() {
            return Err("Invalid alarm.".into());
        }
        let mut time_fields = time.split(":");
        let hour = time_fields.next().ok_or("Missing hour")?;
        let minute = time_fields.next().ok_or("Missing minute")?;
        Ok(AlarmConfig {
            hour: hour.trim().parse().unwrap_or(0),
            minute: minute.trim().parse().unwrap_or(0),
            context: context.trim().to_owned(),
            repeat: AlarmRepeat::from_str(repeat)
                .unwrap_or(AlarmRepeat::Daily),
            device: device.trim().to_owned(),
        })
    }
}
impl fmt::Display for AlarmConfig {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:02}:{:02},{},{},{}", self.hour, self.minute, self.repeat, self.context, self.device)
    }
}

pub enum SpotifyRepeat {
    Off,
    Track,
    Context,
}
impl ToString for SpotifyRepeat {
    fn to_string(&self) -> String {
        match self {
            &SpotifyRepeat::Off => "off".to_string(),
            &SpotifyRepeat::Track => "track".to_string(),
            &SpotifyRepeat::Context => "context".to_string(),
        }
    }
}

#[derive(Serialize)]
struct UriList {
    uris: Vec<String>,
}

#[derive(Serialize)]
struct DeviceIdList {
    device_ids: Vec<String>,
    play: bool,
}

pub type AlarmId = usize;
struct AlarmTimer {
    entry: AlarmEntry,
    #[allow(dead_code)]
    timer: timer::Timer,
    #[allow(dead_code)]
    guard: Option<timer::Guard>,
    channel: Option<Receiver<()>>,
    time: Option<DateTime<Local>>,
    id: usize,
}

pub struct SpotifyConnectr<'a> {
    api: SpotifyEndpoints<'a>,
    settings: settings::Settings,
    auth_code: String,
    access_token: Option<String>,
    refresh_token: Option<String>,
    expire_utc: Option<u64>,
    device: Option<DeviceId>,

    refresh_timer: timer::Timer,
    refresh_timer_guard: Option<timer::Guard>,
    refresh_timer_channel: Option<Receiver<()>>,

    settings_timer: timer::Timer,
    settings_timer_guard: Option<timer::Guard>,
    settings_timer_channel: Option<Receiver<()>>,

    alarms: Vec<AlarmTimer>,
    next_alarm_id: AtomicUsize,

    scrobbler: Option<Scrobbler>,
}
impl<'a> Default for SpotifyConnectr<'a> {
    fn default() -> Self {
        SpotifyConnectr {
            api: SPOTIFY_API,
            settings: Default::default(),
            auth_code: Default::default(),
            access_token: Default::default(),
            refresh_token: Default::default(),
            expire_utc: Default::default(),
            device: Default::default(),
            refresh_timer: timer::Timer::new(),
            refresh_timer_guard: Default::default(),
            refresh_timer_channel: Default::default(),
            settings_timer: timer::Timer::new(),
            settings_timer_guard: Default::default(),
            settings_timer_channel: Default::default(),
            alarms: Vec::new(),
            next_alarm_id: AtomicUsize::new(0),
            scrobbler: None,
        }
    }
}

pub struct SpotifyConnectrBuilder<'a> {
    api: SpotifyEndpoints<'a>,
    access: Option<String>,
    refresh: Option<String>,
    expire: Option<u64>,
}
impl<'a> SpotifyConnectrBuilder<'a> {
    pub fn build(&mut self) -> Option<SpotifyConnectr<'a>> {
        let mut settings: settings::Settings = Default::default();
        if self.expire.is_none() {
            settings = match settings::read_settings(self.api.scopes_version) {
                Some(s) => s,
                None => { return None },
            };
            self.expire = settings.expire_utc;
            self.access = settings.access_token.clone();
            self.refresh = settings.refresh_token.clone();
        }
        let mut cnr = SpotifyConnectr {
            api: self.api,
            settings: settings,
            auth_code: String::new(),
            access_token: self.access.clone(),
            refresh_token: self.refresh.clone(),
            expire_utc: self.expire,
            device: None,
            refresh_timer: timer::Timer::new(),
            refresh_timer_guard: None,
            refresh_timer_channel: None,
            settings_timer: timer::Timer::new(),
            settings_timer_guard: Default::default(),
            settings_timer_channel: Default::default(),
            alarms: Vec::new(),
            next_alarm_id: AtomicUsize::new(0),
            scrobbler: None,
        };
        let alarms: Vec<AlarmConfig> = cnr.settings.alarms.clone();
        for alarm in &alarms {
            let _ = cnr.schedule_alarm(alarm.into());
        }
        cnr.scrobbler_authenticate();
        let _ = cnr.schedule_settings_refresh();
        Some(cnr)
    }
    #[cfg(test)]
    fn with_api(&mut self, api: SpotifyEndpoints<'a>) -> &mut Self {
        self.api = api;
        self
    }
    #[cfg(test)]
    fn with_oauth_tokens(&mut self, access: &str, refresh: &str, expire: u64) -> &mut Self {
        self.access = Some(access.to_string());
        self.refresh = Some(refresh.to_string());
        self.expire = Some(expire);
        self
    }
}


impl<'a> SpotifyConnectr<'a> {
    pub fn new() -> SpotifyConnectrBuilder<'a> {
        SpotifyConnectrBuilder {
            api: SPOTIFY_API,
            access: None,
            refresh: None,
            expire: None,
        }
    }
    fn scrobbler_authenticate(&mut self) {
        let scrobbler = match self.settings.lastfm_enabled {
            false => None,
            true => match self.settings.lastfm {
                None => None,
                Some(ref fm) => {
                    let mut scrob = Scrobbler::new(fm.key.to_owned(), fm.secret.to_owned());
                    scrob.authenticate_with_session_key(fm.session_key.to_owned());
                    Some(scrob)
                }
            }
        };
        self.scrobbler = scrobbler;
    }
    pub fn reread_settings(&mut self) {
        if let Some(settings) = settings::read_settings(self.api.scopes_version) {
            let ids: Vec<AlarmId> = self.alarms.iter().map(|x| {x.id}).collect();
            for id in ids {
                let _ = self.alarm_disable(id);
            }
            self.alarms.clear();
            for alarm in &settings.alarms {
                let _ = self.schedule_alarm(alarm.into());
            }
            self.settings = settings;
        }
        self.scrobbler_authenticate();
    }
    pub fn quick_save_playlist(&self, context: &str) -> Option<&str> {
        self.settings.quick_save_playlist(context)
    }
    fn expire_offset_to_utc(&self, expires_in: u64) -> u64 {
        let now = time::now_utc().to_timespec().sec as u64;
        now + expires_in
    }
    fn expire_utc_to_offset(&self, expire_utc: u64) -> u64 {
        let now = time::now_utc().to_timespec().sec as i64;
        let offset = expire_utc as i64 - now;
        match offset {
            x if x > 0 => x as u64,
            _ => 0,
        }
    }
    fn alarm_current_time(_entry: &AlarmEntry) -> DateTime<Local> {
        // Allow unit tests to override 'now', so days can be deterministic
        #[cfg(test)]
        return match _entry.now {
            Some(dt) => dt.clone(),
            None => Local::now(),
        };
        #[cfg(not(test))]
        Local::now()
    }
    fn next_alarm_datetime(entry: &AlarmEntry) -> Result<DateTime<Local>, String> {
        let now = Self::alarm_current_time(&entry);
        //let format_12h = "%I:%M %p";
        let format_24h = "%H:%M";
        let alarm_time = match time::strptime(&entry.time, format_24h) {
            Ok(t) => t,
            _ => return Err("Could not parse alarm clock time.".to_string()),
        };
        let mut alarm = Self::alarm_current_time(&entry)
            .with_hour(alarm_time.tm_hour as u32).ok_or("Invalid hour")?
            .with_minute(alarm_time.tm_min as u32).ok_or("Invalid minute")?
            .with_second(0).ok_or("Invalid second")?
            .with_nanosecond(0).ok_or("Invalid nanosecond")?;
        // Increment by one day if the current hour has already passed
        if alarm < now {
            alarm = alarm + chrono::Duration::days(1);
        }
        // Increment until a day is found that matches the alarm repeat options
        loop {
            let is_weekday = match alarm.weekday() {
                Weekday::Sat | Weekday::Sun => false,
                _ => true,
            };
            if entry.repeat == AlarmRepeat::Daily ||
                (is_weekday && entry.repeat == AlarmRepeat::Weekdays) ||
                (!is_weekday && entry.repeat == AlarmRepeat::Weekends) {
                    break;
            }
            alarm = alarm + chrono::Duration::days(1);
        }
        Ok(alarm)
    }
    fn alarm_with_id(&mut self, id: AlarmId) -> Result<&mut AlarmTimer, ()> {
        let mut result = self.alarms
            .iter_mut()
            .filter(|x| {x.id == id})
            .collect::<Vec<&mut AlarmTimer>>();
        match result.pop() {
            Some(alarm) => Ok(alarm),
            _ => Err(()),
        }
    }
    pub fn alarm_time(&mut self, id: AlarmId) -> Result<DateTime<Local>, ()> {
        let alarm = self.alarm_with_id(id)?;
        match alarm.time {
            Some(time) => Ok(time),
            _ => Err(()),
        }
    }
    pub fn alarm_disable(&mut self, id: AlarmId) -> Result<(), ()> {
        let alarm = self.alarm_with_id(id)?;
        alarm.guard = None;
        alarm.channel = None;
        alarm.time = None;
        Ok(())
    }
    pub fn alarm_enabled(&mut self, id: AlarmId) -> bool {
        let alarm = self.alarm_with_id(id);
        match alarm {
            Ok(alarm) => alarm.guard.is_some(),
            _ => false,
        }
    }
    pub fn alarm_reschedule(&mut self, id: AlarmId) -> Result<(), ()> {
        let alarm = { self.alarm_with_id(id)? };
        let alarm_time = Self::next_alarm_datetime(&alarm.entry).unwrap();
        let (tx, rx) = channel::<>();

        let closure = move || {
            tx.send(()).unwrap_or_else(|_| { warn!("Alarm clock skipped."); });
        };
        let guard = alarm.timer.schedule_with_date(alarm_time, closure);
        let duration = alarm_time.signed_duration_since(Local::now());
        info!("Alarm {} set for {} hours from now ({} mins)", alarm.entry.time,
              duration.num_hours(), duration.num_minutes());

        alarm.channel = Some(rx);
        alarm.guard = Some(guard);
        alarm.time = Some(alarm_time);
        Ok(())
    }
    pub fn alarm_configure(&mut self, devices: Option<&ConnectDeviceList>) {
        let alarm_config = settings::request_web_alarm_config(&self.settings.alarms, devices);
        if settings::save_web_alarm_config(alarm_config).is_ok() {
            if let Some(settings) = settings::read_settings(self.api.scopes_version) {
                let ids: Vec<AlarmId> = self.alarms.iter().map(|x| {x.id}).collect();
                for id in ids {
                    let _ = self.alarm_disable(id);
                }
                self.alarms.clear();
                for alarm in &settings.alarms {
                    let _ = self.schedule_alarm(alarm.into());
                }
                self.settings = settings;
            };
        }
    }
    pub fn schedule_alarm(&mut self, entry: AlarmEntry) -> Result<AlarmId, ()> {
        let timer = timer::Timer::new();
        let id = self.next_alarm_id.fetch_add(1, Ordering::SeqCst);
        self.alarms.push(AlarmTimer {
            entry: entry,
            timer: timer,
            guard: None,
            channel: None,
            time: None,
            id: id,
        });
        self.alarm_reschedule(id)?;
        Ok(id as AlarmId)
    }
    fn schedule_settings_refresh(&mut self) -> Result<(), ()> {
        let (tx, rx) = channel::<()>();
        let closure = move || {
            tx.send(()).unwrap_or_else(|_| { warn!("Settings refresh lost."); });
        };
        self.settings_timer_channel = Some(rx);
        let expire_offset = chrono::Duration::seconds(60*60); // refresh every hour
        self.settings_timer_guard = Some(self.settings_timer.schedule_with_delay(expire_offset, closure));
        Ok(())
    }
    fn schedule_token_refresh(&mut self) -> Result<(), ()> {
        match self.expire_utc {
            Some(expire_utc) => {
                let (tx, rx) = channel::<()>();
                self.refresh_timer_channel = Some(rx);
                let expire_offset = self.expire_utc_to_offset(expire_utc) as i64;
                // Refresh a bit before it expires
                let expire_offset = match expire_offset {
                    x if x > 60 => x - 60,
                    _ => expire_offset,
                };
                let expire_offset = chrono::Duration::seconds(expire_offset);
                info!("Refreshing Spotify credentials in {} sec", expire_offset.num_seconds());
                let closure = move || {
                    tx.send(()).unwrap_or_else(|_| { warn!("Token refresh lost."); });
                };
                self.refresh_timer_guard = Some(self.refresh_timer.schedule_with_delay(expire_offset, closure));
                Ok(())
            }
            _ => Err(())
        }
    }
    pub fn refresh_access_token(&mut self) {
        info!("Refreshing Spotify credentials now.");
        self.refresh_timer_channel = None;
        match self.refresh_oauth_tokens() {
            Some((access_token, expires_in)) => {
                self.access_token = Some(access_token.clone());
                self.expire_utc = Some(self.expire_offset_to_utc(expires_in));
            },
            None => {
                warn!("Credential refresh rejected.  Attempting to reauthenticate.");
                self.authenticate();
            }
        }
        //let (access_token, expires_in) = ;

        info!("Refreshed credentials.");
        let _ = self.schedule_token_refresh();

        let access_token = self.access_token.clone().unwrap();
        let refresh_token = self.refresh_token.clone().unwrap();
        let _ = settings::save_tokens(self.api.scopes_version,
                                      &access_token,
                                      &refresh_token,
                                      self.expire_utc.unwrap());
    }
    pub fn await_once(&mut self, blocking: bool) {
        // Choose between blocking or non-blocking receive.
        let recv_fn: Box<dyn Fn(&Receiver<()>) -> bool> = match blocking {
            true  => Box::new(move |rx| { match rx.recv() { Ok(_) => true, Err(_) => false } }),
            false => Box::new(move |rx| { match rx.try_recv() { Ok(_) => true, Err(_) => false } }),
        };

        // Get list of which alarms have expired
        let expired = self.alarms.iter().enumerate()
            .map(|(_idx, alarm)| {
                match alarm.channel {
                    Some(ref rx) => rx.try_recv().is_ok(),
                    _ => false,
                }
            }).collect::<Vec<_>>();
        // For each expired alarm, remove it, execute it, and reschedule it
        for (idx, exp) in expired.iter().enumerate() {
            if *exp {
                let old_alarm = self.alarms.swap_remove(idx);
                info!("Alarm started: {} on {}",
                      old_alarm.entry.context.context_uri.as_ref().unwrap(),
                      old_alarm.entry.device);
                self.set_target_device(Some(old_alarm.entry.device.clone()));
                self.play(Some(&old_alarm.entry.context));
                self.set_target_device(None);
                let id = old_alarm.id;
                self.alarms.push(old_alarm);
                let _ = self.alarm_reschedule(id);
            }
        }

        let need_refresh = match self.refresh_timer_channel.as_ref() {
            Some(rx) => recv_fn(rx),
            _ => false,
        };
        let need_settings = match self.settings_timer_channel.as_ref() {
            Some(rx) => recv_fn(rx),
            _ => false,
        };
        if need_settings {
            info!("Re-reading settings file (timer).");
            self.reread_settings();
            let _ = self.schedule_settings_refresh();
        }
        if !need_refresh {
            return ()
        }
        self.refresh_access_token();
    }
    pub fn authenticate(&mut self) {
        info!("Requesting fresh credentials.");
        self.auth_code = http::authenticate(self.api.scopes, self.api.authorize, &self.settings);
        if let Some((access_token, refresh_token, expires_in)) = self.request_oauth_tokens(&self.auth_code, &self.settings) {
            let expire_utc = self.expire_offset_to_utc(expires_in);
            let _ = settings::save_tokens(self.api.scopes_version, &access_token,
                                          &refresh_token, expire_utc);
            self.access_token = Some(access_token);
            self.refresh_token = Some(refresh_token);
            self.expire_utc = Some(expire_utc);
        }
        else {
            let expire_utc = self.expire_offset_to_utc(30);
            self.expire_utc = Some(expire_utc);
        }
        let _ = self.schedule_token_refresh();
    }
    pub fn request_oauth_tokens(&self, auth_code: &str, settings: &settings::Settings) -> Option<(String, String, u64)> {
        let query = QueryString::new()
            .add("grant_type", "authorization_code")
            .add("code", auth_code)
            .add("redirect_uri", format!("http://127.0.0.1:{}", settings.port))
            .add("client_id", settings.client_id.clone())
            .add("client_secret", settings.secret.clone())
            .build();
        let json_response = http::http(self.api.token, Some(&query), None, http::HttpMethod::POST,
                                       http::AccessToken::None);
        match json_response.code {
            Some(200) => {
                match parse_spotify_token(&json_response.unwrap()) {
                    Some(resp) => Some(resp),
                    None => None,
                }
            },
            _ => { None }
        }
    }
    pub fn connect(&mut self) {
        if self.access_token.is_some() {
            info!("Reusing saved credentials.");
            self.refresh_access_token();
            return ()
        }
        self.authenticate()
    }
    pub fn bearer_token(&self) -> http::AccessToken {
        match self.access_token {
            Some(ref x) => http::AccessToken::Bearer(x),
            None => http::AccessToken::None,
        }
    }
    pub fn basic_token(&self) -> http::AccessToken {
        match self.access_token {
            Some(ref x) => http::AccessToken::Basic(x),
            None => http::AccessToken::None,
        }
    }
    pub fn refresh_oauth_tokens(&self) -> Option<(String, u64)> {
        let query = QueryString::new()
            .add("grant_type", "refresh_token")
            .add("refresh_token", self.refresh_token.as_ref().unwrap())
            .add("client_id", self.settings.client_id.clone())
            .add("client_secret", self.settings.secret.clone())
            .build();
        let json_response = http::http(self.api.token, Some(&query), None,
                                       http::HttpMethod::POST, http::AccessToken::None);
        match json_response.code {
            Some(200) => {
                match parse_spotify_token(&json_response.data.unwrap()) {
                    Some((access_token,_,expires_in)) => Some((access_token, expires_in)),
                    None => match self.access_token {
                        Some(ref token) => Some((token.clone(), 30)),
                        None => None,
                    },
                }
            },
            Some(e) if e == 401 || e == 403 => {
                warn!("Refresh token rejected permanently: Error {}", e);
                None
            },
            e @ _ => {
                warn!("Refresh token rejected temporarily: Error {}", e.unwrap_or(0));
                match self.access_token {
                    Some(ref token) => Some((token.clone(), 30)),
                    None => None
                }
            }
        }
    }
    pub fn request_device_list(&mut self) -> Option<ConnectDeviceList> {
        let json_response = http::http(self.api.devices, None, None,
                                       http::HttpMethod::GET, self.bearer_token());
        match json_response.code {
            Some(200) => serde_json::from_str(&json_response.data.unwrap()).unwrap(),
            Some(401) => {
                warn!("Access token invalid.  Attempting to reauthenticate.");
                self.refresh_access_token();
                None
            }
            _ => None
        }
    }
    pub fn request_player_state(&mut self) -> Option<PlayerState> {
        let json_response = http::http(self.api.player_state, None, None,
                                       http::HttpMethod::GET, self.bearer_token());
        match json_response.code {
            Some(200) => match serde_json::from_str(&json_response.data.unwrap()) {
                Ok(json) => json,
                Err(err) => { info!("json error: {}", err); None },
            },
            Some(202) => {
                warn!("Spotify returned no state.");
                Default::default()
            }
            Some(401) => {
                warn!("Access token invalid.  Attempting to reauthenticate.");
                self.refresh_access_token();
                Default::default()
            }
            _ => Default::default()
        }
    }
    pub fn set_target_device(&mut self, device: Option<DeviceId>) {
        self.device = device;
    }
    pub fn play(&self, context: Option<&PlayContext>) -> SpotifyResponse {
        let query = QueryString::new().add_opt("device_id", self.device.clone()).build();
        let body = match context {
            Some(x) => serde_json::to_string(x).unwrap(),
            None => String::new(),
        };
        http::http(self.api.play, Some(&query), Some(&body), http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn pause(&self) -> SpotifyResponse {
        let query = QueryString::new().add_opt("device_id", self.device.clone()).build();
        http::http(self.api.pause, Some(&query), None, http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn next(&self) -> SpotifyResponse {
        let query = QueryString::new().add_opt("device_id", self.device.clone()).build();
        http::http(self.api.next, Some(&query), None, http::HttpMethod::POST, self.bearer_token())
    }
    pub fn previous(&self) -> SpotifyResponse {
        let query = QueryString::new().add_opt("device_id", self.device.clone()).build();
        http::http(self.api.previous, Some(&query), None, http::HttpMethod::POST, self.bearer_token())
    }
    pub fn seek(&self, position: u32) -> SpotifyResponse {
        let query = QueryString::new()
            .add_opt("device_id", self.device.clone())
            .add("position_ms", position)
            .build();
        http::http(self.api.seek, Some(&query), None, http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn volume(&self, volume: u32) -> SpotifyResponse {
        let query = QueryString::new()
            .add_opt("device_id", self.device.clone())
            .add("volume_percent", volume)
            .build();
        http::http(self.api.volume, Some(&query), None, http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn shuffle(&self, shuffle: bool) -> SpotifyResponse {
        let query = QueryString::new()
            .add_opt("device_id", self.device.clone())
            .add("state", shuffle)
            .build();
        http::http(self.api.shuffle, Some(&query), None, http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn repeat(&self, repeat: SpotifyRepeat) -> SpotifyResponse {
        let query = QueryString::new()
            .add_opt("device_id", self.device.clone())
            .add("state", repeat)
            .build();
        http::http(self.api.repeat, Some(&query), None, http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn transfer_multi(&mut self, devices: Vec<String>, play: bool) -> SpotifyResponse {
        let body = serde_json::to_string(&DeviceIdList {device_ids: devices, play: play}).unwrap();
        http::http(self.api.player, None, Some(&body), http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn transfer(&mut self, device: String, play: bool) -> SpotifyResponse {
        let body = serde_json::to_string(&DeviceIdList {device_ids: vec![device.clone()], play: play}).unwrap();
        http::http(self.api.player, None, Some(&body), http::HttpMethod::PUT, self.bearer_token())
    }
    pub fn save_track(&mut self, track: String, playlist: String) -> SpotifyResponse {
        let playlist_id = playlist.split(":").last().unwrap();
        let user_id = playlist.split(":").nth(2).unwrap();
        let uri = format!("{}/{}/playlists/{}/tracks",
                          self.api.add_to_playlist,
                          user_id, playlist_id);

        let body = serde_json::to_string(&UriList {uris: vec![track.clone()]}).unwrap();
        http::http(&uri, None, Some(&body), http::HttpMethod::POST, self.bearer_token())
    }
    pub fn get_presets(&mut self) -> &Vec<(String,String)> {
        &self.settings.presets
    }
    fn device_can_scrobble(&self, device_type: &str) -> bool {
        if let Some(ref fm) = self.settings.lastfm {
            let dev = device_type.to_lowercase();
            if fm.ignore_pc && dev == "computer" {
                return false;
            }
            if fm.ignore_phone && ["smartphone".to_string(),"tablet".to_string()].contains(&dev) {
                return false;
            }
        }
        true
    }
    pub fn scrobbler_now_playing(&mut self, artist: String, track: String,
                                 album: String, device_type: String) {
        if self.device_can_scrobble(&device_type) {
            if let Some(ref scrobbler) = self.scrobbler {
                let s = Scrobble::new(artist, track.clone(), album);
                match scrobbler.now_playing(s) {
                    Ok(_) => { info!("Scrobbler now playing: {}", track); },
                    Err(e) => {error!("Scrobbler update failed: {}", e)},
                }
            }
        }
    }
    pub fn scrobble(&mut self, artist: String, track: String,
                    album: String, device_type: String) {
        if self.device_can_scrobble(&device_type) {
            if let Some(ref scrobbler) = self.scrobbler {
                let s = Scrobble::new(artist, track.clone(), album);
                match scrobbler.scrobble(s) {
                    Ok(_) => { info!("Scrobbled: {}", track); },
                    Err(e) => {error!("Scrobbler update failed: {}", e)},
                }
            }
        }
    }
    pub fn settings(&self) -> &settings::Settings {
        &self.settings
    }
}