librespot/src/main.rs

172 lines
5.6 KiB
Rust
Raw Normal View History

extern crate getopts;
2015-07-09 19:08:56 +00:00
extern crate librespot;
extern crate rpassword;
extern crate env_logger;
2016-03-24 09:31:33 +00:00
#[macro_use]
extern crate log;
2015-04-25 20:32:07 +00:00
2016-01-01 23:16:12 +00:00
use rpassword::read_password;
2015-06-23 14:38:29 +00:00
use std::clone::Clone;
use std::fs::File;
use std::io::{stdout, Read, Write};
use std::path::PathBuf;
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;
2016-01-02 02:30:24 +00:00
use librespot::session::{Bitrate, Config, Session};
use librespot::spirc::SpircManager;
2016-03-17 03:31:57 +00:00
use librespot::version;
2016-03-16 00:05:05 +00:00
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;
2015-04-25 20:32:07 +00:00
fn main() {
if env::var("RUST_LOG").is_err() {
2016-04-24 11:15:53 +00:00
env::set_var("RUST_LOG", "info,librespot=trace")
}
env_logger::init().unwrap();
2016-03-24 09:31:33 +00:00
info!("librespot {} ({}). Built on {}.",
2016-03-17 03:31:57 +00:00
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")
2016-01-02 02:30:24 +00:00
.reqopt("n", "name", "Device name", "NAME")
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
2016-04-24 11:15:53 +00:00
.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
.optflag("", "facebook", "Login with a Facebook account");
2016-01-02 02:30:24 +00:00
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 matches = match opts.parse(&args[1..]) {
2016-01-02 15:19:39 +00:00
Ok(m) => m,
Err(f) => {
2016-04-24 11:15:53 +00:00
error!("Error: {}\n{}", f.to_string(), usage(&*program, &opts));
2016-01-02 15:19:39 +00:00
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.");
2016-01-01 23:16:12 +00:00
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>);
2016-01-02 02:30:24 +00:00
let bitrate = match matches.opt_str("b").as_ref().map(String::as_ref) {
2016-01-02 15:19:39 +00:00
None => Bitrate::Bitrate160, // default value
2016-01-02 02:30:24 +00:00
2016-01-02 15:19:39 +00:00
Some("96") => Bitrate::Bitrate96,
2016-01-02 02:30:24 +00:00
Some("160") => Bitrate::Bitrate160,
Some("320") => Bitrate::Bitrate320,
2016-01-02 15:19:39 +00:00
Some(b) => panic!("Invalid bitrate {}", b),
2016-01-02 02:30:24 +00:00
};
let config = Config {
application_key: appkey,
2016-03-17 03:31:57 +00:00
user_agent: version::version_string(),
2016-01-01 23:16:12 +00:00
device_name: name,
2016-01-02 15:19:39 +00:00
bitrate: bitrate,
};
let stored_credentials = cache.get_credentials();
let session = Session::new(config, cache);
2016-04-24 11:15:53 +00:00
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)
2016-03-16 00:05:05 +00:00
}
2016-04-24 11:15:53 +00:00
(None, _, _) if matches.opt_present("facebook")
=> facebook_login().unwrap(),
(None, _, Some(credentials))
=> credentials,
(None, _, None) if cfg!(feature = "discovery") => {
2016-03-24 09:31:33 +00:00
info!("No username provided and no stored credentials, starting discovery ...");
2016-04-24 11:15:53 +00:00
discovery_login(&session.config().device_name, session.device_id()).unwrap()
}
2016-04-24 11:15:53 +00:00
(None, _, None) => {
error!("No credentials provided");
return
}
};
let reusable_credentials = session.login(credentials).unwrap();
session.cache().put_credentials(&reusable_credentials);
let player = Player::new(session.clone(), move || make_backend());
let spirc = SpircManager::new(session.clone(), player);
2016-01-02 15:19:39 +00:00
thread::spawn(move || spirc.run());
2016-01-01 23:16:12 +00:00
loop {
session.poll();
}
2015-07-01 17:49:03 +00:00
}