summary history branches tags files
commit:74c625aac1df3e1c94cf65ae88fabb990cb2016f
author:Trevor Bentley
committer:Trevor Bentley
date:Mon Apr 10 18:15:38 2017 +0200
parents:44a4e6f165f1a4858eecd754e9fe51b272fadaa2
Modularized
diff --git a/.gitignore b/.gitignore
line changes: +4/-0
index 0000000..0ee4182
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*~
+Cargo.lock
+connectr.ini
+target/

diff --git a/src/http/mod.rs b/src/http/mod.rs
line changes: +178/-0
index e69de29..3fcda88
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -0,0 +1,178 @@
+use std::fmt;
+use std::error::Error;
+use std::str;
+use std::io::{Read, Write, BufReader, BufRead};
+use std::net::{TcpListener};
+
+extern crate regex;
+use self::regex::Regex;
+
+extern crate curl;
+use self::curl::easy::{Easy, List};
+
+extern crate open;
+extern crate url;
+use self::url::percent_encoding;
+
+use super::settings;
+use super::spotify_api;
+
+#[derive(PartialEq)]
+pub enum HttpMethod {
+    GET,
+    POST,
+    PUT,
+}
+
+pub type HttpErrorString = String;
+pub struct HttpResponse {
+    code: Option<u32>,
+    data: Result<String, HttpErrorString>,
+}
+
+impl HttpResponse {
+    pub fn unwrap(self) -> String { self.data.unwrap() }
+    pub fn print(&self) {
+        let code: i32 = match self.code {
+            Some(x) => { x as i32 }
+            None => -1
+        };
+        println!("Code: {}", code);
+        match self.data {
+            Ok(ref s) => {println!("{}", s)}
+            Err(ref s) => {println!("ERROR: {}", s)}
+        }
+    }
+}
+
+impl fmt::Display for HttpResponse {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let code: i32 = match self.code {
+            Some(x) => { x as i32 }
+            None => -1
+        };
+        let _ = write!(f, "Code: {}\n", code);
+        match self.data {
+            Ok(ref s) => {write!(f, "{}\n", s)}
+            Err(ref s) => {write!(f, "ERROR: {}\n", s)}
+        }
+    }
+}
+
+pub fn http(url: &str, query: &str, method: HttpMethod, access_token: Option<&str>) -> HttpResponse {
+    let enc_query = percent_encoding::utf8_percent_encode(&query, percent_encoding::QUERY_ENCODE_SET).collect::<String>();
+    let mut data = match method {
+        HttpMethod::POST => { enc_query.as_bytes() }
+        _ => { query.as_bytes() }
+    };
+    let mut response = None;
+    let mut json_bytes = Vec::<u8>::new();
+    {
+        let mut easy = Easy::new();
+        match method {
+            HttpMethod::GET => {
+                let get_url = format!("{}?{}", url, query);
+                easy.url(&get_url).unwrap();
+            }
+            HttpMethod::POST => {
+                easy.url(url).unwrap();
+                easy.post(true).unwrap();
+                easy.post_field_size(data.len() as u64).unwrap();
+            }
+            HttpMethod::PUT => {
+                easy.url(url).unwrap();
+                easy.put(true).unwrap();
+                easy.post_field_size(data.len() as u64).unwrap();
+            }
+        }
+
+        match access_token {
+            Some(access_token) => {
+                let mut list = List::new();
+                let header = format!("Authorization: Bearer {}", access_token);
+                list.append(&header).unwrap();
+                easy.http_headers(list).unwrap();
+            }
+            None => {}
+        }
+
+        {
+            let mut transfer = easy.transfer();
+            if method == HttpMethod::POST || method == HttpMethod::PUT {
+                transfer.read_function(|buf| {
+                    Ok(data.read(buf).unwrap_or(0))
+                }).unwrap();
+            }
+            transfer.write_function(|x| {
+                json_bytes.extend(x);
+                Ok(x.len())
+            }).unwrap();
+            match transfer.perform() {
+                Err(x) => {
+                    let result: Result<String,String> = Err(x.description().to_string());
+                    return HttpResponse {code: response, data: result }
+                }
+                _ => {}
+            };
+        }
+        response = match easy.response_code() {
+            Ok(code) => { Some(code) }
+            _ => { None }
+        };
+    }
+    let result: Result<String,String> = match String::from_utf8(json_bytes) {
+        Ok(x) => { Ok(x) }
+        Err(x) => { Err(x.utf8_error().description().to_string()) }
+    };
+    HttpResponse {code: response, data: result }
+}
+
+pub fn authenticate(settings: &settings::Settings) -> String {
+    let scopes = spotify_api::SCOPES.join(" ");
+    let host = format!("http://127.0.0.1:{}", settings.port);
+    let url = format!("{}?client_id={}&response_type=code&scope={}&redirect_uri={}",
+                      spotify_api::AUTHORIZE,settings.client_id, scopes, host);
+    let query = percent_encoding::utf8_percent_encode(&url, percent_encoding::QUERY_ENCODE_SET).collect::<String>();
+    let response = "HTTP/1.1 200 OK\r\n\r\n<html><body>
+Authenticated with Spotify.<br/><br/>
+You can close this window.<br/><br/>
+<button type=\"button\" onclick=\"window.open('', '_self', ''); window.close();\">Close</button><br/>
+</body></html>";
+    let auth_lines = oauth_request_with_local_webserver(settings.port, &query, response);
+    let auth_code = spotify_auth_code(auth_lines);
+    auth_code
+}
+
+fn oauth_request_with_local_webserver(port: u32, url: &str, reply: &str) -> Vec<String> {
+    if !open::that(url).is_ok() {
+        return Vec::<String>::new()
+    }
+    let host = format!("127.0.0.1:{}", port);
+    let listener = TcpListener::bind(host).unwrap();
+    let stream = listener.accept().unwrap().0;
+    let mut reader = BufReader::new(stream);
+    let mut response = Vec::<String>::new();
+    for line in reader.by_ref().lines() {
+        let line_str = line.unwrap();
+        response.push(line_str.clone());
+        if line_str == "" {
+            break;
+        }
+    }
+    let _ = reader.into_inner().write(reply.as_bytes());
+    response
+}
+
+fn spotify_auth_code(lines: Vec<String>) -> String {
+    let mut auth_code = String::new();
+    for line in lines {
+        let line_str = line;
+        let re = Regex::new(r"code=([^?& ]+)").unwrap();
+        let ismatch = re.is_match(line_str.as_str());
+        if ismatch {
+            let cap = re.captures(line_str.as_str()).unwrap();
+            auth_code = auth_code + &cap[1];
+        }
+    }
+    auth_code
+}

diff --git a/src/lib.rs b/src/lib.rs
line changes: +15/-0
index 3883215..dea4e52
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,16 @@
 pub mod http;
+pub mod settings;
+pub mod webapi;
+
+pub use webapi::*;
+
+extern crate rustc_serialize;
+
+pub mod spotify_api {
+    pub const SCOPES: &'static [&'static str] = &["user-read-private", "streaming", "user-read-playback-state"];
+    pub const AUTHORIZE: &'static str = "https://accounts.spotify.com/en/authorize";
+    pub const TOKEN: &'static str = "https://accounts.spotify.com/api/token";
+    pub const DEVICES: &'static str = "https://api.spotify.com/v1/me/player/devices";
+    pub const PLAYER_STATE: &'static str = "https://api.spotify.com/v1/me/player";
+    pub const PLAY: &'static str = "https://api.spotify.com/v1/me/player/play";
+}

diff --git a/src/main.rs b/src/main.rs
line changes: +23/-313
index c58058c..15be247
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,326 +1,36 @@
-mod spotify_api {
-    pub const SCOPES: &'static [&'static str] = &["user-read-private", "streaming", "user-read-playback-state"];
-    pub const AUTHORIZE: &'static str = "https://accounts.spotify.com/en/authorize";
-    pub const TOKEN: &'static str = "https://accounts.spotify.com/api/token";
-    pub const DEVICES: &'static str = "https://api.spotify.com/v1/me/player/devices";
-    pub const PLAYER_STATE: &'static str = "https://api.spotify.com/v1/me/player";
-    pub const PLAY: &'static str = "https://api.spotify.com/v1/me/player/play";
-}
-
-#[derive(PartialEq)]
-enum HttpMethod {
-    GET,
-    POST,
-    PUT,
-}
 
 extern crate connectr;
-extern crate curl;
-extern crate open;
-extern crate regex;
-extern crate rustc_serialize;
-extern crate url;
-extern crate ini;
-extern crate systray;
-
-use std::net::{TcpListener};
-use std::io::{Read, Write, BufReader, BufRead};
+use connectr::settings;
 
 use std::process;
-use std::str;
-use std::fmt;
-use std::error::Error;
-use curl::easy::{Easy, List};
-use regex::Regex;
-use rustc_serialize::json::Json;
-use rustc_serialize::json;
-use rustc_serialize::Decodable;
-use rustc_serialize::Decoder;
-use url::percent_encoding;
-use ini::Ini;
-use connectr::http;
-
-fn oauth_request_with_local_webserver(port: u32, url: &str, reply: &str) -> Vec<String> {
-    if !open::that(url).is_ok() {
-        return Vec::<String>::new()
-    }
-    let host = format!("127.0.0.1:{}", port);
-    let listener = TcpListener::bind(host).unwrap();
-    let stream = listener.accept().unwrap().0;
-    let mut reader = BufReader::new(stream);
-    let mut response = Vec::<String>::new();
-    for line in reader.by_ref().lines() {
-        let line_str = line.unwrap();
-        response.push(line_str.clone());
-        if line_str == "" {
-            break;
-        }
-    }
-    let _ = reader.into_inner().write(reply.as_bytes());
-    response
-}
-
-fn spotify_auth_code(lines: Vec<String>) -> String {
-    let mut auth_code = String::new();
-    for line in lines {
-        let line_str = line;
-        let re = Regex::new(r"code=([^?& ]+)").unwrap();
-        let ismatch = re.is_match(line_str.as_str());
-        if ismatch {
-            let cap = re.captures(line_str.as_str()).unwrap();
-            auth_code = auth_code + &cap[1];
-        }
-    }
-    auth_code
-}
-
-macro_rules! vec_of_strings {
-    ($($x:expr),*) => (vec![$($x.to_string()),*]);
-}
-
-fn authenticate(settings: &Settings) -> String {
-    let scopes = spotify_api::SCOPES.join(" ");
-    let host = format!("http://127.0.0.1:{}", settings.port);
-    let url = format!("{}?client_id={}&response_type=code&scope={}&redirect_uri={}",
-                      spotify_api::AUTHORIZE,settings.client_id, scopes, host);
-    let query = percent_encoding::utf8_percent_encode(&url, percent_encoding::QUERY_ENCODE_SET).collect::<String>();
-    let response = "HTTP/1.1 200 OK\r\n\r\n<html><body>
-Authenticated with Spotify.<br/><br/>
-You can close this window.<br/><br/>
-<button type=\"button\" onclick=\"window.open('', '_self', ''); window.close();\">Close</button><br/>
-</body></html>";
-    let auth_lines = oauth_request_with_local_webserver(settings.port, &query, response);
-    let auth_code = spotify_auth_code(auth_lines);
-    auth_code
-}
-
-type HttpErrorString = String;
-struct HttpResponse {
-    code: Option<u32>,
-    data: Result<String, HttpErrorString>,
-}
-
-impl HttpResponse {
-    fn unwrap(self) -> String { self.data.unwrap() }
-    fn print(&self) {
-        let code: i32 = match self.code {
-            Some(x) => { x as i32 }
-            None => -1
-        };
-        println!("Code: {}", code);
-        match self.data {
-            Ok(ref s) => {println!("{}", s)}
-            Err(ref s) => {println!("ERROR: {}", s)}
-        }
-    }
-}
-
-impl std::fmt::Display for HttpResponse {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let code: i32 = match self.code {
-            Some(x) => { x as i32 }
-            None => -1
-        };
-        write!(f, "Code: {}\n", code);
-        match self.data {
-            Ok(ref s) => {write!(f, "{}\n", s)}
-            Err(ref s) => {write!(f, "ERROR: {}\n", s)}
-        }        
-    }
-}
-
-fn http(url: &str, query: &str, method: HttpMethod, access_token: Option<&str>) -> HttpResponse {
-    let mut data = query.as_bytes();
-    //let mut response = 0;
-    let mut response = None;
-    let mut json_bytes = Vec::<u8>::new();
-    {
-        let mut easy = Easy::new();
-        match method {
-            HttpMethod::GET => {
-                let get_url = format!("{}?{}", url, query);
-                easy.url(&get_url).unwrap();
-            }
-            HttpMethod::POST => {
-                easy.url(url).unwrap();
-                easy.post(true).unwrap();
-                easy.post_field_size(data.len() as u64).unwrap();
-            }
-            HttpMethod::PUT => {
-                easy.url(url).unwrap();
-                easy.put(true).unwrap();
-                easy.post_field_size(data.len() as u64).unwrap();
-            }
-        }
-
-        match access_token {
-            Some(access_token) => {
-                let mut list = List::new();
-                let header = format!("Authorization: Bearer {}", access_token);
-                list.append(&header).unwrap();
-                easy.http_headers(list).unwrap();
-            }
-            None => {}
-        }
-
-        {
-            let mut transfer = easy.transfer();
-            if method == HttpMethod::POST || method == HttpMethod::PUT {
-                transfer.read_function(|buf| {
-                    Ok(data.read(buf).unwrap_or(0))
-                }).unwrap();
-            }
-            transfer.write_function(|x| {
-                json_bytes.extend(x);
-                Ok(x.len())
-            }).unwrap();
-            match transfer.perform() {
-                Err(x) => {
-                    let result: Result<String,String> = Err(x.description().to_string());
-                    return HttpResponse {code: response, data: result }
-                }
-                _ => {}
-            };
-        }
-        response = match easy.response_code() {
-            Ok(code) => { Some(code) }
-            _ => { None }
-        };
-    }
-    let result: Result<String,String> = match String::from_utf8(json_bytes) {
-        Ok(x) => { Ok(x) }
-        Err(x) => { Err(x.utf8_error().description().to_string()) }
-    };
-    HttpResponse {code: response, data: result }
-}
-
-fn parse_spotify_token(json: &str) -> (String, String) {
-    let json_data = Json::from_str(&json).unwrap();
-    let obj = json_data.as_object().unwrap();
-    let access_token = obj.get("access_token").unwrap().as_string().unwrap();
-    let refresh_token = obj.get("refresh_token").unwrap().as_string().unwrap();
-    (String::from(access_token),String::from(refresh_token))
-}
-
-//#[derive(RustcDecodable, RustcEncodable, Debug)]
-#[derive(RustcEncodable, Debug)]
-struct ConnectDevice {
-    id: String,
-    is_active: bool,
-    is_restricted: bool,
-    name: String,
-    device_type: String,
-    volume_percent: u32
-}
-
-impl Decodable for ConnectDevice {
-    fn decode<D: Decoder>(d: &mut D) -> Result<ConnectDevice, D::Error> {
-        d.read_struct("ConnectDevice", 6, |d| {
-            let id = try!(d.read_struct_field("id", 0, |d| { d.read_str() }));
-            let is_active = try!(d.read_struct_field("is_active", 1, |d| { d.read_bool() }));
-            let is_restricted = try!(d.read_struct_field("is_restricted", 2, |d| { d.read_bool() }));
-            let name = try!(d.read_struct_field("name", 3, |d| { d.read_str() }));
-            let device_type = try!(d.read_struct_field("type", 4, |d| { d.read_str() }));
-            let volume_percent = try!(d.read_struct_field("volume_percent", 5, |d| { d.read_u32() }));
-            Ok(ConnectDevice{ id: id,
-                              is_active: is_active,
-                              is_restricted: is_restricted,
-                              name: name,
-                              device_type: device_type,
-                              volume_percent: volume_percent})
-        })
-    }
-}
-
-#[derive(RustcDecodable, RustcEncodable)]
-struct ConnectDeviceList {
-    devices: Vec<ConnectDevice>
-}
-
-#[derive(RustcDecodable, RustcEncodable, Debug)]
-struct PlayerState {
-    timestamp: u64,
-    device: ConnectDevice,
-    progress_ms: u32,
-    is_playing: bool,
-    shuffle_state: bool,
-    repeat_state: String,
-}
 
-struct Settings {
-    port: u32,
-    secret: String,
-    client_id: String,
-}
-
-#[cfg(target_os = "windows")]
-fn systray(player_state: PlayerState) {
-    let mut app;
-    match systray::Application::new() {
-        Ok(w) => app = w,
-        Err(e) => panic!("Can't create systray window.")
-    }
-    let mut w = &mut app.window;
-    let _ = w.set_icon_from_file(&"spotify.ico".to_string());
-    let _ = w.set_tooltip(&"Whatever".to_string());
-    let _ = w.add_menu_item(&"Print a thing".to_string(), |window| {
-        println!("Printing a thing!");
-    });
-    println!("Waiting on message!");
-    w.wait_for_message();
-}
-
-fn read_settings() -> Option<Settings> {
-    let conf = Ini::load_from_file("connectr.ini").unwrap();
-
-    let section = conf.section(Some("connectr".to_owned())).unwrap();
-    let port = section.get("port").unwrap().parse().unwrap();
-
-    let section = conf.section(Some("application".to_owned())).unwrap();
-    let secret = section.get("secret").unwrap();
-    let client_id = section.get("client_id").unwrap();
-    if client_id.starts_with('<') || secret.starts_with('<') {
-        println!("");
-        println!("ERROR: Spotify Client ID or Secret not set in connectr.ini!");
-        println!("");
-        println!("Create a Spotify application at https://developer.spotify.com/my-applications/ and");
-        println!("add the client ID and secret to connectr.ini.");
-        println!("");
-        println!("Be sure to add a redirect URI of http://127.0.0.1:<PORT> to your Spotify application,");
-        println!("and make sure the port matches in connectr.ini.");
-        println!("");
-        return None;
-    }
-    Some(Settings { secret: secret.to_string(), client_id: client_id.to_string(), port: port })
-}
+//extern crate systray;
+
+//#[cfg(target_os = "windows")]
+//fn systray(player_state: PlayerState) {
+//    let mut app;
+//    match systray::Application::new() {
+//        Ok(w) => app = w,
+//        Err(e) => panic!("Can't create systray window.")
+//    }
+//    let mut w = &mut app.window;
+//    let _ = w.set_icon_from_file(&"spotify.ico".to_string());
+//    let _ = w.set_tooltip(&"Whatever".to_string());
+//    let _ = w.add_menu_item(&"Print a thing".to_string(), |window| {
+//        println!("Printing a thing!");
+//    });
+//    println!("Waiting on message!");
+//    w.wait_for_message();
+//}
 
 fn main() {
-    let settings = match read_settings() {
+    let settings = match settings::read_settings() {
         Some(s) => s,
         None => process::exit(0),
     };
-    let auth_code = authenticate(&settings);
-    let query = format!("grant_type=authorization_code&code={}&redirect_uri=http://127.0.0.1:{}&client_id={}&client_secret={}",
-                        auth_code, settings.port, settings.client_id, settings.secret);
-    let query = percent_encoding::utf8_percent_encode(&query, percent_encoding::QUERY_ENCODE_SET).collect::<String>();
-    let json_response = http(spotify_api::TOKEN, &query, HttpMethod::POST, None).unwrap();
-    let (access_token, refresh_token) = parse_spotify_token(&json_response);
-
-    let json_response = http(spotify_api::DEVICES, "", HttpMethod::GET, Some(&access_token)).unwrap();
-    let device_list: ConnectDeviceList = json::decode(&json_response).unwrap();
-
-    let json_response = http(spotify_api::PLAYER_STATE, "", HttpMethod::GET, Some(&access_token)).unwrap();
-    let player_state: PlayerState = json::decode(&json_response).unwrap();
-
-    println!("Auth Code: {}...", &auth_code[0..5]);
-    println!("Access: {}... / Refresh: {}...", &access_token[0..5], &refresh_token[0..5]);
-    for dev in device_list.devices {
-        println!("{:?}", dev);
-    }
-    println!("State: {:?}", player_state);
-
-    let query = format!("{{\"context_uri\": \"spotify:user:mrmekon:playlist:4XqYlbPdDUsranzjicPCgf\"}}");
-    let json_response = http(spotify_api::PLAY, &query, HttpMethod::PUT, Some(&access_token));
+    let mut spotify = connectr::SpotifyConnectr::new(settings);
+    spotify.connect();
+    spotify.go();
 
     //systray(player_state);
     //loop {}

diff --git a/src/settings/mod.rs b/src/settings/mod.rs
line changes: +32/-0
index 0000000..e2485d7
--- /dev/null
+++ b/src/settings/mod.rs
@@ -0,0 +1,32 @@
+extern crate ini;
+use self::ini::Ini;
+
+pub struct Settings {
+    pub port: u32,
+    pub secret: String,
+    pub client_id: String,
+}
+
+pub fn read_settings() -> Option<Settings> {
+    let conf = Ini::load_from_file("connectr.ini").unwrap();
+
+    let section = conf.section(Some("connectr".to_owned())).unwrap();
+    let port = section.get("port").unwrap().parse().unwrap();
+
+    let section = conf.section(Some("application".to_owned())).unwrap();
+    let secret = section.get("secret").unwrap();
+    let client_id = section.get("client_id").unwrap();
+    if client_id.starts_with('<') || secret.starts_with('<') {
+        println!("");
+        println!("ERROR: Spotify Client ID or Secret not set in connectr.ini!");
+        println!("");
+        println!("Create a Spotify application at https://developer.spotify.com/my-applications/ and");
+        println!("add the client ID and secret to connectr.ini.");
+        println!("");
+        println!("Be sure to add a redirect URI of http://127.0.0.1:<PORT> to your Spotify application,");
+        println!("and make sure the port matches in connectr.ini.");
+        println!("");
+        return None;
+    }
+    Some(Settings { secret: secret.to_string(), client_id: client_id.to_string(), port: port })
+}

diff --git a/src/webapi/mod.rs b/src/webapi/mod.rs
line changes: +105/-0
index 0000000..081b9ff
--- /dev/null
+++ b/src/webapi/mod.rs
@@ -0,0 +1,105 @@
+extern crate rustc_serialize;
+use self::rustc_serialize::{Decodable, Decoder, json};
+use self::rustc_serialize::json::Json;
+
+use super::http;
+use super::settings;
+use super::spotify_api;
+
+pub fn parse_spotify_token(json: &str) -> (String, String) {
+    let json_data = Json::from_str(&json).unwrap();
+    let obj = json_data.as_object().unwrap();
+    let access_token = obj.get("access_token").unwrap().as_string().unwrap();
+    let refresh_token = obj.get("refresh_token").unwrap().as_string().unwrap();
+    (String::from(access_token),String::from(refresh_token))
+}
+
+//#[derive(RustcDecodable, RustcEncodable, Debug)]
+#[derive(RustcEncodable, Debug)]
+pub struct ConnectDevice {
+    pub id: String,
+    pub is_active: bool,
+    pub is_restricted: bool,
+    pub name: String,
+    pub device_type: String,
+    pub volume_percent: u32
+}
+
+impl Decodable for ConnectDevice {
+    fn decode<D: Decoder>(d: &mut D) -> Result<ConnectDevice, D::Error> {
+        d.read_struct("ConnectDevice", 6, |d| {
+            let id = try!(d.read_struct_field("id", 0, |d| { d.read_str() }));
+            let is_active = try!(d.read_struct_field("is_active", 1, |d| { d.read_bool() }));
+            let is_restricted = try!(d.read_struct_field("is_restricted", 2, |d| { d.read_bool() }));
+            let name = try!(d.read_struct_field("name", 3, |d| { d.read_str() }));
+            let device_type = try!(d.read_struct_field("type", 4, |d| { d.read_str() }));
+            let volume_percent = try!(d.read_struct_field("volume_percent", 5, |d| { d.read_u32() }));
+            Ok(ConnectDevice{ id: id,
+                              is_active: is_active,
+                              is_restricted: is_restricted,
+                              name: name,
+                              device_type: device_type,
+                              volume_percent: volume_percent})
+        })
+    }
+}
+
+#[derive(RustcDecodable, RustcEncodable)]
+pub struct ConnectDeviceList {
+    pub devices: Vec<ConnectDevice>
+}
+
+#[derive(RustcDecodable, RustcEncodable, Debug)]
+pub struct PlayerState {
+    pub timestamp: u64,
+    pub device: ConnectDevice,
+    pub progress_ms: u32,
+    pub is_playing: bool,
+    pub shuffle_state: bool,
+    pub repeat_state: String,
+}
+
+pub fn request_oauth_tokens(auth_code: &str, settings: &settings::Settings) -> (String, String) {
+    let query = format!("grant_type=authorization_code&code={}&redirect_uri=http://127.0.0.1:{}&client_id={}&client_secret={}",
+                        auth_code, settings.port, settings.client_id, settings.secret);
+    let json_response = http::http(spotify_api::TOKEN, &query, http::HttpMethod::POST, None).unwrap();
+    parse_spotify_token(&json_response)
+}
+
+pub struct SpotifyConnectr {
+    settings: settings::Settings,
+    auth_code: String,
+    access_token: String,
+    refresh_token: String,
+}
+
+impl SpotifyConnectr {
+    pub fn new(settings: settings::Settings) -> SpotifyConnectr {
+        SpotifyConnectr {settings: settings, auth_code: String::new(),
+                         access_token: String::new(), refresh_token: String::new()}
+    }
+    pub fn connect(&mut self) {
+        self.auth_code = http::authenticate(&self.settings);
+        let (access_token, refresh_token) = request_oauth_tokens(&self.auth_code, &self.settings);
+        self.access_token = access_token;
+        self.refresh_token = refresh_token;
+    }
+    pub fn request_device_list() {}
+    pub fn go(&self) {
+        let json_response = http::http(spotify_api::DEVICES, "", http::HttpMethod::GET, Some(&self.access_token)).unwrap();
+        let device_list: ConnectDeviceList = json::decode(&json_response).unwrap();
+
+        let json_response = http::http(spotify_api::PLAYER_STATE, "", http::HttpMethod::GET, Some(&self.access_token)).unwrap();
+        let player_state: PlayerState = json::decode(&json_response).unwrap();
+
+        println!("Auth Code: {}...", &self.auth_code[0..5]);
+        println!("Access: {}... / Refresh: {}...", &self.access_token[0..5], &self.refresh_token[0..5]);
+        for dev in device_list.devices {
+            println!("{:?}", dev);
+        }
+        println!("State: {:?}", player_state);
+
+        let query = format!("{{\"context_uri\": \"spotify:user:mrmekon:playlist:4XqYlbPdDUsranzjicPCgf\"}}");
+        let _ = http::http(spotify_api::PLAY, &query, http::HttpMethod::PUT, Some(&self.access_token));
+    }
+}