summary history branches tags files
commit:0389a01b875be3785d766f4b5860862ee6bd2dda
author:Trevor Bentley
committer:Trevor Bentley
date:Mon Aug 7 00:21:58 2017 +0200
parents:ba61b410619db824e752678523d129cacb0fe51b
Web config of alarms partially done
diff --git a/Cargo.toml b/Cargo.toml
line changes: +1/-1
index f627e9a..cae8991
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -42,7 +42,7 @@ url = "1.4.0"
 rust-ini = "0.9"
 time = "0.1"
 timer = {path = "deps/timer.rs", version="0.1.4"} # Modified for chrono 0.3.1
-chrono = "0.3"
+chrono = "0.4"
 log = "0.3.7"
 log4rs = "0.6.3"
 ctrlc = "3.0.1"

diff --git a/src/main.rs b/src/main.rs
line changes: +27/-11
index cee5317..08bbf4b
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,7 +18,6 @@ use fruitbasket::FruitError;
 
 extern crate timer;
 extern crate chrono;
-use chrono::offset::utc::UTC;
 
 extern crate ctrlc;
 use std::sync::atomic::{AtomicBool, Ordering};
@@ -74,6 +73,7 @@ enum CallbackAction {
     Redraw,
     Reconfigure,
     SaveTrack,
+    EditAlarms,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -627,6 +627,17 @@ fn fill_menu<T: TStatusBar>(app: &mut ConnectrApp,
     }
 
     status.add_separator();
+    let cb: NSCallback = Box::new(move |sender, tx| {
+        let cmd = MenuCallbackCommand {
+            action: CallbackAction::EditAlarms,
+            sender: sender,
+            data: String::new(),
+        };
+        let _ = tx.send(serde_json::to_string(&cmd).unwrap());
+    });
+    let _ = status.add_item("Edit Alarms", cb, false);
+
+    status.add_separator();
     let cb: NSCallback = Box::new(move |_sender, _tx| {
         let _ = open::that("https://open.spotify.com/search/");
     });
@@ -702,6 +713,7 @@ fn handle_callback(player_state: Option<&connectr::PlayerState>,
             }
         }
         CallbackAction::Reconfigure => {}
+        CallbackAction::EditAlarms => {}
     }
     refresh
 }
@@ -810,6 +822,17 @@ fn create_spotify_thread(rx_cmd: Receiver<String>) -> SpotifyThread {
             *preset_writer = spotify.get_presets().clone();
             let _ = tx.send(SpotifyThreadCommand::Update);
         }
+        let alarm = spotify.schedule_alarm(connectr::AlarmEntry {
+            time: "19:46".to_string(),
+            repeat: connectr::AlarmRepeat::Daily,
+            context: connectr::PlayContext::new()
+                .context_uri("spotify:user:mrmekon:playlist:2NZx9rQlpDTEhjrbCIwh0Q")
+                .offset_position(0)
+                .build(),
+            device: "adf47bd71923ad681f33e3a778c56fc33f4a63a8".to_string(),
+        }).unwrap();
+        let _ = spotify.alarm_disable(alarm);
+        let _ = spotify.alarm_reschedule(alarm);
         loop {
             if rx.try_recv().is_ok() {
                 // Main thread tells us to shutdown
@@ -823,6 +846,9 @@ fn create_spotify_thread(rx_cmd: Receiver<String>) -> SpotifyThread {
             if let Ok(s) = rx_cmd.recv_timeout(Duration::from_millis(200)) {
                 info!("Received {}", s);
                 let cmd: MenuCallbackCommand = serde_json::from_str(&s).unwrap();
+                if cmd.action == CallbackAction::EditAlarms {
+                    spotify.alarm_configure();
+                }
                 let refresh_strategy =  handle_callback(player_state.read().unwrap().as_ref(),
                                                         &mut spotify, &cmd);
                 refresh_time_utc = match refresh_strategy {
@@ -935,16 +961,6 @@ fn main() {
     let mut touchbar = TouchbarUI::init(tx);
     info!("Created touchbar.");
 
-    spotify.schedule_alarm(connectr::AlarmEntry {
-        time: "18:34".to_string(),
-        repeat: connectr::AlarmRepeat::Daily,
-        context: connectr::PlayContext::new()
-            .context_uri("spotify:user:mrmekon:playlist:2NZx9rQlpDTEhjrbCIwh0Q")
-            .offset_position(0)
-            .build(),
-        device: "adf47bd71923ad681f33e3a778c56fc33f4a63a8".to_string(),
-    });
-
     let mut tiny: Option<process::Child> = None;
     if let Some(wine_dir) = find_wine_path() {
         info!("Found wine root: {:?}", wine_dir);

diff --git a/src/settings/mod.rs b/src/settings/mod.rs
line changes: +92/-0
index 2fc8411..4f60a8e
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -1,12 +1,15 @@
 extern crate ini;
 use self::ini::Ini;
 use super::http;
+use super::AlarmRepeat;
+use super::AlarmConfig;
 
 extern crate time;
 extern crate fruitbasket;
 
 use std::env;
 use std::path;
+use std::str::FromStr;
 use std::collections::BTreeMap;
 
 const INIFILE: &'static str = "connectr.ini";
@@ -24,6 +27,7 @@ pub struct Settings {
     pub presets: Vec<(String,String)>,
     pub default_quicksave: Option<String>,
     pub quicksave: BTreeMap<String, String>,
+    pub alarms: Vec<AlarmConfig>,
 }
 
 impl Settings {
@@ -126,6 +130,36 @@ If something goes wrong or changes, edit or delete that file.</small>
     config
 }
 
+pub fn request_web_alarm_config() -> BTreeMap<String,String> {
+    let form = format!(r###"
+{}
+<!DOCTYPE HTML>
+<html><head><title>Connectr Alarm Clocks</title><style> tr:nth-child(even) {{ background: #f2f2f2; }} </style></head>
+<body><h2>Connectr Alarm Clocks</h2> Input in 24-hour time format:
+<form method="POST" action="#" accept-charset="UTF-8"><table>
+<tr><th style="width:100px; border-bottom: 1px solid #ddd;" align="center">Time</th><th align="center" style="width:100px;border-bottom: 1px solid #ddd;">Repeat</th></tr>
+
+<tr><td align="center"><input style="width:40px;" type="number" name="hour_0" min="0" max="23" size="3" maxlength="2">:<input style="width:40px;" type="number" name="minute_0" min="0" max="59" size="3" maxlength="2"></td><td align="center"><select name="repeat_0"><option value="daily">Daily</option><option value="weekdays">Weekdays</option><option value="weekends">Weekends</option></select></td></tr>
+
+<tr><td align="center"><input style="width:40px;" type="number" name="hour_1" min="0" max="23" size="3" maxlength="2">:<input style="width:40px;" type="number" name="minute_1" min="0" max="59" size="3" maxlength="2"></td><td align="center"><select name="repeat_1"><option value="daily">Daily</option><option value="weekdays">Weekdays</option><option value="weekends">Weekends</option></select></td></tr>
+
+<tr><td align="center"><input style="width:40px;" type="number" name="hour_2" min="0" max="23" size="3" maxlength="2">:<input style="width:40px;" type="number" name="minute_2" min="0" max="59" size="3" maxlength="2"></td><td align="center"><select name="repeat_2"><option value="daily">Daily</option><option value="weekdays">Weekdays</option><option value="weekends">Weekends</option></select></td></tr>
+
+<tr><td align="center"><input style="width:40px;" type="number" name="hour_3" min="0" max="23" size="3" maxlength="2">:<input style="width:40px;" type="number" name="minute_3" min="0" max="59" size="3" maxlength="2"></td><td align="center"><select name="repeat_3"><option value="daily">Daily</option><option value="weekdays">Weekdays</option><option value="weekends">Weekends</option></select></td></tr>
+
+<tr><td align="center"><input style="width:40px;" type="number" name="hour_4" min="0" max="23" size="3" maxlength="2">:<input style="width:40px;" type="number" name="minute_4" min="0" max="59" size="3" maxlength="2"></td><td align="center"><select name="repeat_4"><option value="daily">Daily</option><option value="weekdays">Weekdays</option><option value="weekends">Weekends</option></select></td></tr>
+
+<tr><td colspan=2><center><input type="submit" value="Save Configuration" style="height:50px; width: 300px; font-size:20px;"></center></td></tr></br>
+</table></form>
+</body></html>
+"###, "HTTP/1.1 200 OK\r\n\r\n");
+    let reply = format!("{}Configuration saved.  You can close this window.",
+                        "HTTP/1.1 200 OK\r\n\r\n");
+    let mut config = BTreeMap::<String,String>::new();
+    config.append(&mut http::config_request_local_webserver(WEB_PORT, form, reply));
+    config
+}
+
 pub fn save_web_config(mut config: BTreeMap<String,String>) -> Ini {
     let mut c = Ini::new();
     let port = config.remove("port").unwrap();
@@ -156,6 +190,54 @@ pub fn save_web_config(mut config: BTreeMap<String,String>) -> Ini {
     c
 }
 
+pub fn save_web_alarm_config(config: BTreeMap<String,String>) -> Result<(), SettingsError> {
+    let file = inifile();
+    let mut conf: Ini;
+    match Ini::load_from_file(&file) {
+        Ok(c) => conf = c,
+        Err(_) => return Err("Couldn't open configuration.".to_string()),
+    }
+    let mut entries: Vec<AlarmConfig> = Vec::with_capacity(5);
+    for _ in 0..5 {
+        entries.push(Default::default());
+    }
+    for pair in config.iter() {
+        let key = pair.0;
+        let value = pair.1;
+        // are you kidding me??
+        let idx = key.chars().rev().take(1).collect::<Vec<char>>()[0].to_digit(10).unwrap() as usize;
+        let entry = entries.get_mut(idx).unwrap();
+        match key.split("_").next().unwrap() {
+            "hour" => {
+                if let Ok(val) = value.parse() {
+                    entry.hour = val;
+                }
+            },
+            "minute" => {
+                if let Ok(val) = value.parse() {
+                    entry.minute = val;
+                }
+            },
+            "repeat" => {
+                entry.repeat = match value.as_ref() {
+                    "weekdays" => AlarmRepeat::Weekdays,
+                    "weekends" => AlarmRepeat::Weekends,
+                    _ => AlarmRepeat::Daily,
+                };
+            },
+            _ => {},
+        }
+    }
+    for i in 0..5 {
+        info!("Entry {}: {:?}", i, entries.get(i).unwrap());
+    }
+    for (idx,entry) in entries.iter().enumerate() {
+        conf.set_to(Some("alarms"), format!("alarm{}", idx+1), entry.to_string());
+    }
+    conf.write_to_file(&file).unwrap();
+    Ok(())
+}
+
 pub fn read_settings(scopes_version: u32) -> Option<Settings> {
     info!("Attempting to read config file.");
     let conf = match Ini::load_from_file(&inifile()) {
@@ -224,12 +306,22 @@ pub fn read_settings(scopes_version: u32) -> Option<Settings> {
             }
         }
     }
+    let mut alarms = Vec::<AlarmConfig>::new();
+    if let Some(section) = conf.section(Some("alarms".to_owned())) {
+        for (_key, value) in section {
+            match AlarmConfig::from_str(value) {
+                Ok(a) => alarms.push(a),
+                Err(_) => {},
+            }
+        }
+    }
 
     Some(Settings { secret: secret.to_string(), client_id: client_id.to_string(), port: port,
                     access_token: access, refresh_token: refresh, expire_utc: expire_utc,
                     presets: presets,
                     default_quicksave: quicksave_default,
                     quicksave: quicksave,
+                    alarms: alarms,
     })
 }
 

diff --git a/src/webapi/mod.rs b/src/webapi/mod.rs
line changes: +148/-21
index f7ab374..cb02f57
--- a/src/webapi/mod.rs
+++ b/src/webapi/mod.rs
@@ -5,12 +5,15 @@ extern crate time;
 extern crate timer;
 
 extern crate chrono;
-use self::chrono::{DateTime, Local, UTC, TimeZone, Datelike, Timelike, Weekday};
+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;
@@ -272,12 +275,35 @@ impl QueryString {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Debug)]
 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 {
+            "weekdays" => Ok(AlarmRepeat::Weekdays),
+            "weekends" => Ok(AlarmRepeat::Weekends),
+            _ => Ok(AlarmRepeat::Daily),
+        }
+    }
+}
 
 pub struct AlarmEntry {
     pub time: String,
@@ -288,6 +314,40 @@ pub struct AlarmEntry {
     pub now: Option<DateTime<Local>>,
 }
 
+#[derive(Default, Debug)]
+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")?;
+        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.parse().unwrap(),
+            minute: minute.parse().unwrap(),
+            context: context.to_owned(),
+            repeat: AlarmRepeat::from_str(repeat).unwrap(),
+            device: device.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,
@@ -314,11 +374,16 @@ struct DeviceIdList {
     play: bool,
 }
 
+pub type AlarmId = usize;
 struct AlarmTimer {
     entry: AlarmEntry,
+    #[allow(dead_code)]
     timer: timer::Timer,
-    guard: timer::Guard,
-    channel: Receiver<()>,
+    #[allow(dead_code)]
+    guard: Option<timer::Guard>,
+    channel: Option<Receiver<()>>,
+    time: Option<DateTime<Local>>,
+    id: usize,
 }
 
 pub struct SpotifyConnectr<'a> {
@@ -335,6 +400,7 @@ pub struct SpotifyConnectr<'a> {
     refresh_timer_channel: Option<Receiver<()>>,
 
     alarms: Vec<AlarmTimer>,
+    next_alarm_id: AtomicUsize,
 }
 impl<'a> Default for SpotifyConnectr<'a> {
     fn default() -> Self {
@@ -349,6 +415,8 @@ impl<'a> Default for SpotifyConnectr<'a> {
             refresh_timer: timer::Timer::new(),
             refresh_timer_guard: Default::default(),
             refresh_timer_channel: Default::default(),
+            alarms: Vec::new(),
+            next_alarm_id: AtomicUsize::new(0),
         }
     }
 }
@@ -382,6 +450,7 @@ impl<'a> SpotifyConnectrBuilder<'a> {
                               refresh_timer_guard: None,
                               refresh_timer_channel: None,
                               alarms: Vec::new(),
+                              next_alarm_id: AtomicUsize::new(0),
         })
     }
     #[cfg(test)]
@@ -423,24 +492,25 @@ impl<'a> SpotifyConnectr<'a> {
             _ => 0,
         }
     }
-    fn alarm_current_time(&self, entry: &AlarmEntry) -> DateTime<Local> {
+    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 {
+        return match _entry.now {
             Some(dt) => dt.clone(),
             None => Local::now(),
         };
+        #[cfg(not(test))]
         Local::now()
     }
-    fn next_alarm_datetime(&self, entry: &AlarmEntry) -> Result<DateTime<Local>, String> {
-        let now = self.alarm_current_time(&entry);
-        let format_12h = "%I:%M %p";
+    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)
+        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")?
@@ -464,21 +534,73 @@ impl<'a> SpotifyConnectr<'a> {
         }
         Ok(alarm)
     }
-    pub fn schedule_alarm(&mut self, entry: AlarmEntry) -> Result<DateTime<Local>, ()> {
-        let alarm = self.next_alarm_datetime(&entry).unwrap();
+    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 mut alarm = { self.alarm_with_id(id)? };
+        let alarm_time = Self::next_alarm_datetime(&alarm.entry).unwrap();
         let (tx, rx) = channel::<>();
-        let timer = timer::Timer::new();
+
         let closure = move || { tx.send(()).unwrap(); };
-        let guard = timer.schedule_with_date(alarm, closure);
-        let duration = alarm.signed_duration_since(Local::now());
-        info!("Alarm set for {} hours from now", duration.num_hours());
+        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)", 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) {
+        let alarm_config = settings::request_web_alarm_config();
+        if settings::save_web_alarm_config(alarm_config).is_ok() {
+            if let Some(settings) = settings::read_settings(self.api.scopes_version) {
+                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: guard,
-            channel: rx,
+            guard: None,
+            channel: None,
+            time: None,
+            id: id,
         });
-        Ok(alarm)
+        self.alarm_reschedule(id)?;
+        Ok(id as AlarmId)
     }
     fn schedule_token_refresh(&mut self) -> Result<(), ()> {
         match self.expire_utc {
@@ -533,14 +655,19 @@ impl<'a> SpotifyConnectr<'a> {
 
         // Get list of which alarms have expired
         let expired = self.alarms.iter().enumerate()
-            .map(|(idx, alarm)| alarm.channel.try_recv().is_ok()).collect::<Vec<_>>();
+            .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);
                 self.set_target_device(Some(old_alarm.entry.device.clone()));
                 self.play(Some(&old_alarm.entry.context));
-                self.schedule_alarm(old_alarm.entry);
+                let _ = self.schedule_alarm(old_alarm.entry);
             }
         }
 

diff --git a/src/webapi/test.rs b/src/webapi/test.rs
line changes: +20/-10
index d935018..f5eeb50
--- a/src/webapi/test.rs
+++ b/src/webapi/test.rs
@@ -6,7 +6,7 @@ mod tests {
     extern crate time;
 
     extern crate chrono;
-    use self::chrono::UTC;
+    use webapi::chrono::TimeZone;
 
     use super::super::*;
     use super::super::super::SpotifyEndpoints;
@@ -192,7 +192,10 @@ mod tests {
 
     #[test]
     fn test_alarm_scheduler() {
-        let mut spotify = SpotifyConnectr::new().with_api(TEST_API);
+        let mut spotify = SpotifyConnectr::new()
+            .with_api(TEST_API)
+            .build()
+            .unwrap();
 
         // From a Friday morning
         let today = Local.ymd(2017, 06, 16).and_hms_milli(9, 00, 00, 0);
@@ -200,22 +203,22 @@ mod tests {
         // Daily, later the same day
         let entry = build_alarm_entry("21:00", AlarmRepeat::Daily, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-16 21:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-16 21:00:00");
 
         // Daily, the next day
         let entry = build_alarm_entry("08:00", AlarmRepeat::Daily, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-17 08:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-17 08:00:00");
 
         // Weekends, the next day
         let entry = build_alarm_entry("08:00", AlarmRepeat::Weekends, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-17 08:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-17 08:00:00");
 
         // Weekdays, the next week
         let entry = build_alarm_entry("08:00", AlarmRepeat::Weekdays, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-19 08:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-19 08:00:00");
 
         // From a Wednesday night
         let today = Local.ymd(2017, 06, 21).and_hms_milli(21, 00, 00, 0);
@@ -223,21 +226,28 @@ mod tests {
         // Daily, later the same day
         let entry = build_alarm_entry("23:00", AlarmRepeat::Daily, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-21 23:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-21 23:00:00");
 
         // Daily, the next day
         let entry = build_alarm_entry("00:00", AlarmRepeat::Daily, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-22 00:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-22 00:00:00");
 
         // Weekends, 3 days later
         let entry = build_alarm_entry("08:00", AlarmRepeat::Weekends, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-24 08:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-24 08:00:00");
 
         // Weekdays, the next day
         let entry = build_alarm_entry("08:00", AlarmRepeat::Weekdays, today.clone());
         let alarm = spotify.schedule_alarm(entry).unwrap();
-        assert_eq!(alarm.format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-22 08:00:00");
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-22 08:00:00");
+        // Disable/re-enable and verify it responds correctly
+        assert_eq!(spotify.alarm_enabled(alarm), true);
+        assert!(spotify.alarm_disable(alarm).is_ok());
+        assert_eq!(spotify.alarm_enabled(alarm), false);
+        assert!(spotify.alarm_time(alarm).is_err());
+        assert!(spotify.alarm_reschedule(alarm).is_ok());
+        assert_eq!(spotify.alarm_time(alarm).unwrap().format("%Y-%m-%d %H:%M:%S").to_string(), "2017-06-22 08:00:00");
     }
 }