summary history branches tags files
commit:e3f44d6f2d99ae07ce074b28ae9739ed2773d803
author:Trevor Bentley
committer:Trevor Bentley
date:Tue Jan 16 22:20:56 2018 +0100
parents:adbb8d766b0ea558275c3114a3bbf4a43391bb21
Various stability fixes
diff --git a/Cargo.toml b/Cargo.toml
line changes: +5/-3
index cae8991..cbb575d
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,19 +58,21 @@ systray = {path = "deps/systray-rs", version="0.1.1-connectr"}
 
 [target."cfg(windows)".dependencies.rubrail]
 default-features=false
-version = "0.5"
+version = "0.6"
 
 [target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
 
 [target."cfg(all(unix, not(target_os = \"macos\")))".dependencies.rubrail]
 default-features = false
-version = "0.5"
+version = "0.6"
 
 [target."cfg(target_os = \"macos\")".dependencies]
 cocoa = "0.9"
 objc-foundation = "0.1.1"
 objc_id = "0.1"
-rubrail = "0.5"
+
+[target."cfg(target_os = \"macos\")".dependencies.rubrail]
+version = "0.6"
 
 [target."cfg(target_os = \"macos\")".dependencies.objc]
 version = "0.2.2"

diff --git a/src/http/mod.rs b/src/http/mod.rs
line changes: +4/-1
index 4229684..27b472a
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -21,7 +21,7 @@ use self::url::percent_encoding;
 
 use super::settings;
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Debug)]
 pub enum HttpMethod {
     GET,
     POST,
@@ -63,6 +63,7 @@ impl fmt::Display for HttpResponse {
     }
 }
 
+#[derive(Debug)]
 pub enum AccessToken<'a> {
     Bearer(&'a str),
     Basic(&'a str),
@@ -72,6 +73,8 @@ pub enum AccessToken<'a> {
 pub fn http(url: &str, query: Option<&str>, body: Option<&str>,
             method: HttpMethod, access_token: AccessToken) -> HttpResponse {
     let mut headers = List::new();
+    #[cfg(feature = "verbose_http")]
+    info!("HTTP URL: {:?} {}\nQuery: {:?}\nBody: {:?}\nToken: {:?}", method, url, query, body, access_token);
     let data = match method {
         HttpMethod::POST => {
             match query {

diff --git a/src/main.rs b/src/main.rs
line changes: +15/-3
index ceebd1a..9e9013a
--- a/src/main.rs
+++ b/src/main.rs
@@ -126,13 +126,19 @@ impl TScrubberData for TouchbarScrubberData {
         self.entries.borrow().len() as u32
     }
     fn text(&self, _item: rubrail::ItemId, idx: u32) -> String {
-        self.entries.borrow()[idx as usize].0.to_string()
+        match self.entries.borrow().get(idx as usize) {
+            Some(e) => e.0.to_string(),
+            None => String::new(),
+        }
     }
     fn width(&self, _item: rubrail::ItemId, idx: u32) -> u32 {
         // 10px per character + some padding seems to work nicely for the default
         // font.  no idea what it's like on other machines.  does the touchbar
         // font change? ¯\_(ツ)_/¯
-        let len = self.entries.borrow()[idx as usize].0.len() as u32;
+        let len = match self.entries.borrow().get(idx as usize) {
+            Some(e) => e.0.len() as u32,
+            None => 1,
+        };
         let width = len * 8 + 20;
         width
     }
@@ -463,7 +469,9 @@ fn fill_menu<T: TStatusBar>(app: &mut ConnectrApp,
     let presets = spotify.presets.read().unwrap();
     if device_list.is_none() ||
         player_state.is_none() {
-            // TODO: handle empty groups
+            status.add_label("No devices found.");
+            status.add_separator();
+            status.add_quit("Exit");
             return;
         }
     let device_list = device_list.as_ref().unwrap();
@@ -1050,4 +1058,8 @@ fn play_uri(spotify: &mut connectr::SpotifyConnectr, device: Option<&str>, uri: 
             require(spotify.play(None));
         }
     };
+
+    // Always set it back to None, so commands go to the currently
+    // playing device.
+    spotify.set_target_device(None);
 }

diff --git a/src/osx/rustnsobject.rs b/src/osx/rustnsobject.rs
line changes: +2/-2
index 4f39bd3..f975896
--- a/src/osx/rustnsobject.rs
+++ b/src/osx/rustnsobject.rs
@@ -132,7 +132,7 @@ impl INSObject for ObjcSubclass {
                     let rustdata: &mut RustWrapperClass = &mut *(ptr as *mut RustWrapperClass);
                     if let Some(ref cb) = rustdata.cb_fn {
                         // Ownership?  Fuck ownership!
-                        let mut rustdata: &mut RustWrapperClass = &mut *(ptr as *mut RustWrapperClass);
+                        let rustdata: &mut RustWrapperClass = &mut *(ptr as *mut RustWrapperClass);
                         cb(rustdata, sender);
                     }
                 }
@@ -143,7 +143,7 @@ impl INSObject for ObjcSubclass {
             extern fn objc_get_rust_data(this: &Object, _cmd: Sel) -> u64 {
                 unsafe {*this.get_ivar("_rustdata")}
             }
-            
+
             unsafe {
                 let f: extern fn(&mut Object, Sel, u64) = objc_cb;
                 decl.add_method(sel!(cb:), f);

diff --git a/src/webapi/mod.rs b/src/webapi/mod.rs
line changes: +33/-20
index 80dcc81..79e2c19
--- a/src/webapi/mod.rs
+++ b/src/webapi/mod.rs
@@ -38,10 +38,11 @@ pub fn parse_spotify_token(json: &str) -> (String, String, u64) {
         let expires_in = json_data["expires_in"].as_u64().unwrap_or(0 as u64);
         return (String::from(access_token),String::from(refresh_token), expires_in);
     }
-    (String::new(), String::new(), 0)
+    // Default to blank, expiring in 5s to trigger a retry.
+    (String::new(), String::new(), 5000)
 }
 
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Debug, Default)]
 pub struct ConnectDevice {
     pub id: Option<String>,
     pub is_active: bool,
@@ -119,7 +120,7 @@ pub struct ConnectContext {
     pub uri: String,
 }
 
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Debug, Default)]
 pub struct PlayerState {
     pub timestamp: i64,
     pub device: ConnectDevice,
@@ -587,11 +588,13 @@ impl<'a> SpotifyConnectr<'a> {
         }
     }
     pub fn alarm_reschedule(&mut self, id: AlarmId) -> Result<(), ()> {
-        let mut alarm = { self.alarm_with_id(id)? };
+        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(); };
+        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,
@@ -645,7 +648,9 @@ impl<'a> SpotifyConnectr<'a> {
                 };
                 let expire_offset = chrono::Duration::seconds(expire_offset);
                 info!("Refreshing Spotify credentials in {} sec", expire_offset.num_seconds());
-                let closure = move || { tx.send(()).unwrap(); };
+                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(())
             }
@@ -700,6 +705,7 @@ impl<'a> SpotifyConnectr<'a> {
                       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);
@@ -728,16 +734,22 @@ impl<'a> SpotifyConnectr<'a> {
         let _ = self.schedule_token_refresh();
     }
     pub fn request_oauth_tokens(&self, auth_code: &str, settings: &settings::Settings) -> (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 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).unwrap();
-        parse_spotify_token(&json_response)
+                                       http::AccessToken::None);
+        match json_response.code {
+            Some(200) => {
+                parse_spotify_token(&json_response.unwrap())
+            },
+            // Default to blank, expiring in 5s to trigger a retry.
+            _ => { (String::new(), String::new(), 5000) }
+        }
     }
     pub fn connect(&mut self) {
         if self.access_token.is_some() {
@@ -797,12 +809,16 @@ impl<'a> SpotifyConnectr<'a> {
                 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();
-                None
+                Default::default()
             }
-            _ => None
+            _ => Default::default()
         }
     }
     pub fn set_target_device(&mut self, device: Option<DeviceId>) {
@@ -857,14 +873,11 @@ impl<'a> SpotifyConnectr<'a> {
         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 device = devices[0].clone();
         let body = serde_json::to_string(&DeviceIdList {device_ids: devices, play: play}).unwrap();
-        self.set_target_device(Some(device));
         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();
-        self.set_target_device(Some(device));
         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 {