diff --git a/src/audio_backend/mod.rs b/src/audio_backend/mod.rs index bcb3e554..4b4f6bfe 100644 --- a/src/audio_backend/mod.rs +++ b/src/audio_backend/mod.rs @@ -86,3 +86,11 @@ declare_backends! { ("pipe", &mk_sink::), ]; } + +pub fn find>(name: Option) -> Option<&'static (Fn(Option<&str>) -> Box + Send + Sync)> { + if let Some(name) = name.as_ref().map(AsRef::as_ref) { + BACKENDS.iter().find(|backend| name == backend.0).map(|backend| backend.1) + } else { + Some(BACKENDS.first().expect("No backends were enabled at build time").1) + } +} diff --git a/src/lib.rs b/src/lib.rs index 39bd6566..31671d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ extern crate bit_set; extern crate byteorder; extern crate crypto; -extern crate env_logger; extern crate eventual; extern crate getopts; extern crate hyper; @@ -56,7 +55,6 @@ pub mod cache; pub mod connection; pub mod diffie_hellman; pub mod link; -pub mod main_helper; pub mod metadata; pub mod player; pub mod stream; diff --git a/src/main.rs b/src/main.rs index b9c41654..cfe1b9e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,76 @@ +#[macro_use] extern crate log; extern crate getopts; extern crate librespot; extern crate ctrlc; +extern crate env_logger; +use env_logger::LogBuilder; use std::io::{stderr, Write}; use std::process::exit; use std::thread; +use std::env; +use std::path::PathBuf; +use std::str::FromStr; use librespot::spirc::SpircManager; -use librespot::main_helper; use librespot::authentication::get_credentials; +use librespot::audio_backend::{self, BACKENDS}; +use librespot::cache::{Cache, DefaultCache, NoCache}; +use librespot::player::Player; +use librespot::session::{Bitrate, Config, Session}; +use librespot::version; fn usage(program: &str, opts: &getopts::Options) -> String { let brief = format!("Usage: {} [options]", program); format!("{}", opts.usage(&brief)) } -fn main() { - let mut opts = getopts::Options::new(); - main_helper::add_session_arguments(&mut opts); - main_helper::add_authentication_arguments(&mut opts); - main_helper::add_player_arguments(&mut opts); +fn setup_logging(verbose: bool) { + let mut builder = LogBuilder::new(); - let args: Vec = std::env::args().collect(); + match env::var("RUST_LOG") { + Ok(config) => { + builder.parse(&config); + builder.init().unwrap(); + + if verbose { + warn!("`--verbose` flag overidden by `RUST_LOG` environment variable"); + } + } + Err(_) => { + if verbose { + builder.parse("mdns=info,librespot=trace"); + } else { + builder.parse("mdns=info,librespot=info"); + } + builder.init().unwrap(); + } + } +} + +fn list_backends() { + println!("Available Backends : "); + for (&(name, _), idx) in BACKENDS.iter().zip(0..) { + if idx == 0 { + println!("- {} (default)", name); + } else { + println!("- {}", name); + } + } +} + +fn setup(args: &[String]) -> (Session, Player) { + let mut opts = getopts::Options::new(); + 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") + .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM") + .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM") + .optflag("v", "verbose", "Enable verbose output") + .optopt("u", "username", "Username to sign in with", "USERNAME") + .optopt("p", "password", "Password", "PASSWORD") + .optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND") + .optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -31,14 +80,57 @@ fn main() { } }; - main_helper::setup_logging(&matches); + let verbose = matches.opt_present("verbose"); + setup_logging(verbose); + + info!("librespot {} ({}). Built on {}.", + version::short_sha(), + version::commit_date(), + version::short_now()); + + let backend_name = matches.opt_str("backend"); + if backend_name == Some("?".into()) { + list_backends(); + exit(0); + } + + let backend = audio_backend::find(backend_name.as_ref()) + .expect("Invalid backend"); + + let bitrate = matches.opt_str("b").as_ref() + .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate")) + .unwrap_or(Bitrate::Bitrate160); + + let config = Config { + user_agent: version::version_string(), + device_name: matches.opt_str("name").unwrap(), + bitrate: bitrate, + onstart: matches.opt_str("onstart"), + onstop: matches.opt_str("onstop"), + }; + + let cache = matches.opt_str("c").map(|cache_location| { + Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap()) + as Box + }).unwrap_or_else(|| Box::new(NoCache)); + + let session = Session::new(config, cache); - let session = main_helper::create_session(&matches); let credentials = get_credentials(&session, matches.opt_str("username"), - matches.opt_str("password")); + matches.opt_str("password")); session.login(credentials).unwrap(); - let player = main_helper::create_player(&session, &matches); + let device_name = matches.opt_str("device"); + let player = Player::new(session.clone(), move || { + (backend)(device_name.as_ref().map(AsRef::as_ref)) + }); + + (session, player) +} + +fn main() { + let args: Vec = std::env::args().collect(); + let (session, player) = setup(&args); let spirc = SpircManager::new(session.clone(), player); let spirc_signal = spirc.clone(); diff --git a/src/main_helper.rs b/src/main_helper.rs deleted file mode 100644 index e42660b7..00000000 --- a/src/main_helper.rs +++ /dev/null @@ -1,125 +0,0 @@ -use env_logger::LogBuilder; -use getopts; -use std::env; -use std::path::PathBuf; -use std::process::exit; - -use audio_backend::{BACKENDS, Sink}; -use cache::{Cache, DefaultCache, NoCache}; -use player::Player; -use session::{Bitrate, Config, Session}; -use version; - -pub fn find_backend(name: Option<&str>) -> &'static (Fn(Option<&str>) -> Box + 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 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") - .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM") - .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM") - .optflag("v", "verbose", "Enable verbose output"); -} - -pub fn add_authentication_arguments(opts: &mut getopts::Options) { - opts.optopt("u", "username", "Username to sign in with", "USERNAME") - .optopt("p", "password", "Password", "PASSWORD"); -} - -pub fn add_player_arguments(opts: &mut getopts::Options) { - opts.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND") - .optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE"); -} - -pub fn create_session(matches: &getopts::Matches) -> Session { - info!("librespot {} ({}). Built on {}.", - version::short_sha(), - version::commit_date(), - version::short_now()); - - 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 - }).unwrap_or_else(|| Box::new(NoCache) as Box); - - let onstart = matches.opt_str("onstart"); - let onstop = matches.opt_str("onstop"); - - let config = Config { - user_agent: version::version_string(), - device_name: name, - bitrate: bitrate, - onstart: onstart, - onstop: onstop, - }; - - Session::new(config, cache) -} - -pub fn create_player(session: &Session, matches: &getopts::Matches) -> Player { - let backend_name = matches.opt_str("backend"); - let device_name = matches.opt_str("device"); - - let make_backend = find_backend(backend_name.as_ref().map(AsRef::as_ref)); - - Player::new(session.clone(), move || { - make_backend(device_name.as_ref().map(AsRef::as_ref)) - }) -} - -pub fn setup_logging(matches: &getopts::Matches) { - let verbose = matches.opt_present("verbose"); - let mut builder = LogBuilder::new(); - - match env::var("RUST_LOG") { - Ok(config) => { - builder.parse(&config); - builder.init().unwrap(); - - if verbose { - warn!("`--verbose` flag overidden by `RUST_LOG` environment variable"); - } - } - Err(_) => { - if verbose { - builder.parse("mdns=info,librespot=trace"); - } else { - builder.parse("mdns=info,librespot=info"); - } - builder.init().unwrap(); - } - } -} diff --git a/src/session.rs b/src/session.rs index 1965ec7f..59167614 100644 --- a/src/session.rs +++ b/src/session.rs @@ -10,6 +10,7 @@ use rand::thread_rng; use std::io::{Read, Write, Cursor}; use std::result::Result; use std::sync::{Mutex, RwLock, Arc, mpsc}; +use std::str::FromStr; use album_cover::AlbumCover; use apresolve::apresolve; @@ -34,6 +35,17 @@ pub enum Bitrate { Bitrate160, Bitrate320, } +impl FromStr for Bitrate { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "96" => Ok(Bitrate::Bitrate96), + "160" => Ok(Bitrate::Bitrate160), + "320" => Ok(Bitrate::Bitrate320), + _ => Err(s.into()), + } + } +} pub struct Config { pub user_agent: String,