Browse Source

Add a main helper to make it easier to use librespot.

Paul Lietar 9 years ago
parent
commit
a36325a46a
5 changed files with 173 additions and 131 deletions
  1. 1 0
      src/lib.in.rs
  2. 2 0
      src/lib.rs
  3. 12 131
      src/main.rs
  4. 156 0
      src/main_helper.rs
  5. 2 0
      src/session.rs

+ 1 - 0
src/lib.in.rs

@@ -16,6 +16,7 @@ pub mod session;
 pub mod spirc;
 pub mod link;
 pub mod stream;
+pub mod main_helper;
 
 #[cfg(feature = "facebook")]
 pub mod spotilocal;

+ 2 - 0
src/lib.rs

@@ -14,12 +14,14 @@ extern crate bit_set;
 extern crate byteorder;
 extern crate crypto;
 extern crate eventual;
+extern crate getopts;
 extern crate hyper;
 extern crate lmdb_rs;
 extern crate num;
 extern crate protobuf;
 extern crate shannon;
 extern crate rand;
+extern crate rpassword;
 extern crate rustc_serialize;
 extern crate time;
 extern crate tempfile;

+ 12 - 131
src/main.rs

@@ -1,166 +1,47 @@
 extern crate getopts;
 extern crate librespot;
-extern crate rpassword;
 extern crate env_logger;
 #[macro_use]
 extern crate log;
 
-use rpassword::read_password;
-use std::clone::Clone;
-use std::fs::File;
-use std::io::{stdout, Read, Write};
-use std::path::PathBuf;
+use std::process::exit;
 use std::thread;
 use std::env;
 
-use librespot::audio_backend::BACKENDS;
-use librespot::authentication::{Credentials, facebook_login, discovery_login};
-use librespot::cache::{Cache, DefaultCache, NoCache};
-use librespot::player::Player;
-use librespot::session::{Bitrate, Config, Session};
 use librespot::spirc::SpircManager;
-use librespot::version;
+use librespot::main_helper;
 
 fn usage(program: &str, opts: &getopts::Options) -> String {
     let brief = format!("Usage: {} [options]", program);
     format!("{}", opts.usage(&brief))
 }
 
-#[cfg(feature = "static-appkey")]
-static APPKEY: Option<&'static [u8]> = Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/spotify_appkey.key")));
-#[cfg(not(feature = "static-appkey"))]
-static APPKEY: Option<&'static [u8]> = None;
-
 fn main() {
     if env::var("RUST_LOG").is_err() {
         env::set_var("RUST_LOG", "info,librespot=trace")
     }
     env_logger::init().unwrap();
 
-    info!("librespot {} ({}). Built on {}.",
-             version::short_sha(),
-             version::commit_date(),
-             version::short_now());
-
-    let args: Vec<String> = std::env::args().collect();
-    let program = args[0].clone();
-
     let mut opts = getopts::Options::new();
-    opts.optopt("u", "username", "Username to sign in with", "USERNAME")
-        .optopt("p", "password", "Password", "PASSWORD")
-        .optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
-        .reqopt("n", "name", "Device name", "NAME")
-        .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
-        .optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
-        .optflag("", "facebook", "Login with a Facebook account");
+    main_helper::add_session_arguments(&mut opts);
+    main_helper::add_authentication_arguments(&mut opts);
+    main_helper::add_player_arguments(&mut opts);
 
-    if APPKEY.is_none() {
-        opts.reqopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
-    } else {
-        opts.optopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
-    };
+    let args: Vec<String> = std::env::args().collect();
 
     let matches = match opts.parse(&args[1..]) {
         Ok(m) => m,
         Err(f) => {
-            error!("Error: {}\n{}", f.to_string(), usage(&*program, &opts));
-            return;
-        }
-    };
-
-    let make_backend = match matches.opt_str("backend").as_ref().map(AsRef::as_ref) {
-        Some("?") => {
-            println!("Available Backends : ");
-            for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
-                if idx == 0 {
-                    println!("- {} (default)", name);
-                } else {
-                    println!("- {}", name);
-                }
-            }
-
-            return;
-        },
-        Some(name) => {
-            BACKENDS.iter().find(|backend| name == backend.0).expect("Unknown backend").1
-        },
-        None => {
-            BACKENDS.first().expect("No backends were enabled at build time").1
-        }
-    };
-
-    let appkey = matches.opt_str("a").map(|appkey_path| {
-        let mut file = File::open(appkey_path)
-                            .expect("Could not open app key.");
-
-        let mut data = Vec::new();
-        file.read_to_end(&mut data).unwrap();
-
-        data
-    }).or_else(|| APPKEY.map(ToOwned::to_owned)).unwrap();
-
-    let username = matches.opt_str("u");
-    let name = matches.opt_str("n").unwrap();
-
-    let cache = matches.opt_str("c").map(|cache_location| {
-        Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap()) as Box<Cache + Send + Sync>
-    }).unwrap_or_else(|| Box::new(NoCache) as Box<Cache + Send + Sync>);
-
-    let bitrate = match matches.opt_str("b").as_ref().map(String::as_ref) {
-        None => Bitrate::Bitrate160, // default value
-
-        Some("96") => Bitrate::Bitrate96,
-        Some("160") => Bitrate::Bitrate160,
-        Some("320") => Bitrate::Bitrate320,
-        Some(b) => panic!("Invalid bitrate {}", b),
-    };
-
-    let config = Config {
-        application_key: appkey,
-        user_agent: version::version_string(),
-        device_name: name,
-        bitrate: bitrate,
-    };
-
-    let stored_credentials = cache.get_credentials();
-
-    let session = Session::new(config, cache);
-
-    let credentials = match (username, matches.opt_str("p"), stored_credentials) {
-        (Some(username), Some(password), _)
-            => Credentials::with_password(username, password),
-
-        (Some(ref username), _, Some(ref credentials)) if *username == credentials.username
-            => credentials.clone(),
-
-        (Some(username), None, _) => {
-            print!("Password for {}: ", username);
-            stdout().flush().unwrap();
-            let password = read_password().unwrap();
-            Credentials::with_password(username.clone(), password)
-        }
-
-        (None, _, _) if matches.opt_present("facebook")
-            => facebook_login().unwrap(),
-
-        (None, _, Some(credentials))
-            => credentials,
-
-        (None, _, None) if cfg!(feature = "discovery") => {
-            info!("No username provided and no stored credentials, starting discovery ...");
-            discovery_login(&session.config().device_name, session.device_id()).unwrap()
-        }
-
-        (None, _, None) => {
-            error!("No credentials provided");
-            return
+            error!("Error: {}\n{}", f.to_string(), usage(&args[0], &opts));
+            exit(1)
         }
     };
 
-    let reusable_credentials = session.login(credentials).unwrap();
-    session.cache().put_credentials(&reusable_credentials);
+    let session = main_helper::create_session(&matches);
+    let credentials = main_helper::get_credentials(&session, &matches);
+    session.login(credentials).unwrap();
 
-    let player = Player::new(session.clone(), move || make_backend());
+    let player = main_helper::create_player(&session, &matches);
 
     let spirc = SpircManager::new(session.clone(), player);
     thread::spawn(move || spirc.run());

+ 156 - 0
src/main_helper.rs

@@ -0,0 +1,156 @@
+use getopts;
+use rpassword;
+use std::fs::File;
+use std::io::{stdout, Read, Write};
+use std::path::PathBuf;
+use std::path::Path;
+use std::process::exit;
+
+use audio_backend::{BACKENDS, Sink};
+use authentication::{Credentials, facebook_login, discovery_login};
+use cache::{Cache, DefaultCache, NoCache};
+use player::Player;
+use session::{Bitrate, Config, Session};
+use version;
+
+#[cfg(feature = "static-appkey")]
+static APPKEY: Option<&'static [u8]> = Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/spotify_appkey.key")));
+#[cfg(not(feature = "static-appkey"))]
+static APPKEY: Option<&'static [u8]> = None;
+
+pub fn find_backend(name: Option<&str>) -> &'static (Fn() -> Box<Sink> + Send + Sync) {
+    match name {
+        Some("?") => {
+            println!("Available Backends : ");
+            for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
+                if idx == 0 {
+                    println!("- {} (default)", name);
+                } else {
+                    println!("- {}", name);
+                }
+            }
+
+            exit(0);
+        },
+        Some(name) => {
+            BACKENDS.iter().find(|backend| name == backend.0).expect("Unknown backend").1
+        },
+        None => {
+            BACKENDS.first().expect("No backends were enabled at build time").1
+        }
+    }
+}
+
+pub fn load_appkey<P: AsRef<Path>>(path: Option<P>) -> Vec<u8> {
+    path.map(|path| {
+        let mut file = File::open(path).expect("Could not open app key.");
+
+        let mut data = Vec::new();
+        file.read_to_end(&mut data).unwrap();
+
+        data
+    }).or_else(|| APPKEY.map(ToOwned::to_owned)).unwrap()
+}
+
+pub fn add_session_arguments(opts: &mut getopts::Options) {
+    opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
+        .reqopt("n", "name", "Device name", "NAME")
+        .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE");
+
+    if APPKEY.is_none() {
+        opts.reqopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
+    } else {
+        opts.optopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
+    };
+}
+
+pub fn add_authentication_arguments(opts: &mut getopts::Options) {
+    opts.optopt("u", "username", "Username to sign in with", "USERNAME")
+        .optopt("p", "password", "Password", "PASSWORD");
+
+    if cfg!(feature = "facebook") {
+        opts.optflag("", "facebook", "Login with a Facebook account");
+    }
+}
+
+pub fn add_player_arguments(opts: &mut getopts::Options) {
+    opts.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND");
+}
+
+pub fn create_session(matches: &getopts::Matches) -> Session {
+    info!("librespot {} ({}). Built on {}.",
+             version::short_sha(),
+             version::commit_date(),
+             version::short_now());
+
+    let appkey = load_appkey(matches.opt_str("a"));
+    let name = matches.opt_str("n").unwrap();
+    let bitrate = match matches.opt_str("b").as_ref().map(String::as_ref) {
+        None => Bitrate::Bitrate160, // default value
+
+        Some("96") => Bitrate::Bitrate96,
+        Some("160") => Bitrate::Bitrate160,
+        Some("320") => Bitrate::Bitrate320,
+        Some(b) => {
+            error!("Invalid bitrate {}", b);
+            exit(1)
+        }
+    };
+
+    let cache = matches.opt_str("c").map(|cache_location| {
+        Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap()) as Box<Cache + Send + Sync>
+    }).unwrap_or_else(|| Box::new(NoCache) as Box<Cache + Send + Sync>);
+
+    let config = Config {
+        application_key: appkey,
+        user_agent: version::version_string(),
+        device_name: name,
+        bitrate: bitrate,
+    };
+
+    Session::new(config, cache)
+}
+
+pub fn get_credentials(session: &Session, matches: &getopts::Matches) -> Credentials {
+    let credentials = session.cache().get_credentials();
+
+    match (matches.opt_str("username"),
+           matches.opt_str("password"),
+           credentials) {
+
+        (Some(username), Some(password), _)
+            => Credentials::with_password(username, password),
+
+        (Some(ref username), _, Some(ref credentials)) if *username == credentials.username
+            => credentials.clone(),
+
+        (Some(username), None, _) => {
+            print!("Password for {}: ", username);
+            stdout().flush().unwrap();
+            let password = rpassword::read_password().unwrap();
+            Credentials::with_password(username.clone(), password)
+        }
+
+        (None, _, _) if cfg!(feature = "facebook") && matches.opt_present("facebook")
+            => facebook_login().unwrap(),
+
+        (None, _, Some(credentials))
+            => credentials,
+
+        (None, _, None) if cfg!(feature = "discovery") => {
+            info!("No username provided and no stored credentials, starting discovery ...");
+            discovery_login(&session.config().device_name, session.device_id()).unwrap()
+        }
+
+        (None, _, None) => {
+            error!("No credentials provided");
+            exit(1)
+        }
+    }
+}
+
+pub fn create_player(session: &Session, matches: &getopts::Matches) -> Player {
+    let make_backend = find_backend(matches.opt_str("backend").as_ref().map(AsRef::as_ref));
+
+    Player::new(session.clone(), move || make_backend())
+}

+ 2 - 0
src/session.rs

@@ -225,6 +225,8 @@ impl Session {
                     auth_data: welcome_data.get_reusable_auth_credentials().to_owned(),
                 };
 
+                self.0.cache.put_credentials(&reusable_credentials);
+
                 Ok(reusable_credentials)
             }