Create event loop in main

This commit is contained in:
Paul Lietar 2017-01-18 18:41:22 +00:00
parent 2a0ccc0d1d
commit d27063d5da
9 changed files with 129 additions and 132 deletions

View file

@ -5,10 +5,10 @@ use alsa::{PCM, Stream, Mode, Format, Access};
pub struct AlsaSink(Option<PCM>, String); pub struct AlsaSink(Option<PCM>, String);
impl Open for AlsaSink { impl Open for AlsaSink {
fn open(device: Option<&str>) -> AlsaSink { fn open(device: Option<String>) -> AlsaSink {
info!("Using alsa sink"); info!("Using alsa sink");
let name = device.unwrap_or("default").to_string(); let name = device.unwrap_or("default".to_string());
AlsaSink(None, name) AlsaSink(None, name)
} }

View file

@ -1,7 +1,7 @@
use std::io; use std::io;
pub trait Open { pub trait Open {
fn open(Option<&str>) -> Self; fn open(Option<String>) -> Self;
} }
pub trait Sink { pub trait Sink {
@ -49,8 +49,7 @@ macro_rules! _declare_backends {
) )
} }
#[allow(dead_code)] fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<Sink> {
fn mk_sink<S: Sink + Open + 'static>(device: Option<&str>) -> Box<Sink> {
Box::new(S::open(device)) Box::new(S::open(device))
} }
@ -75,7 +74,7 @@ use self::pipe::StdoutSink;
declare_backends! { declare_backends! {
pub const BACKENDS : &'static [ pub const BACKENDS : &'static [
(&'static str, (&'static str,
&'static (Fn(Option<&str>) -> Box<Sink> + Sync + Send + 'static)) &'static (Fn(Option<String>) -> Box<Sink> + Sync + Send + 'static))
] = &[ ] = &[
#[cfg(feature = "alsa-backend")] #[cfg(feature = "alsa-backend")]
("alsa", &mk_sink::<AlsaSink>), ("alsa", &mk_sink::<AlsaSink>),
@ -87,7 +86,7 @@ declare_backends! {
]; ];
} }
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<&'static (Fn(Option<&str>) -> Box<Sink> + Send + Sync)> { pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<&'static (Fn(Option<String>) -> Box<Sink> + Send + Sync)> {
if let Some(name) = name.as_ref().map(AsRef::as_ref) { if let Some(name) = name.as_ref().map(AsRef::as_ref) {
BACKENDS.iter().find(|backend| name == backend.0).map(|backend| backend.1) BACKENDS.iter().find(|backend| name == backend.0).map(|backend| backend.1)
} else { } else {

View file

@ -7,7 +7,7 @@ use std::slice;
pub struct StdoutSink(Box<Write>); pub struct StdoutSink(Box<Write>);
impl Open for StdoutSink { impl Open for StdoutSink {
fn open(path: Option<&str>) -> StdoutSink { fn open(path: Option<String>) -> StdoutSink {
if let Some(path) = path { if let Some(path) = path {
let file = OpenOptions::new().write(true).open(path).unwrap(); let file = OpenOptions::new().write(true).open(path).unwrap();
StdoutSink(Box::new(file)) StdoutSink(Box::new(file))

View file

@ -39,13 +39,13 @@ fn find_output(device: &str) -> Option<DeviceIndex> {
} }
impl <'a> Open for PortAudioSink<'a> { impl <'a> Open for PortAudioSink<'a> {
fn open(device: Option<&str>) -> PortAudioSink<'a> { fn open(device: Option<String>) -> PortAudioSink<'a> {
debug!("Using PortAudio sink"); debug!("Using PortAudio sink");
portaudio::initialize().unwrap(); portaudio::initialize().unwrap();
let device_idx = match device { let device_idx = match device.as_ref().map(AsRef::as_ref) {
Some("?") => { Some("?") => {
list_outputs(); list_outputs();
exit(0) exit(0)

View file

@ -8,7 +8,7 @@ use std::ffi::CString;
pub struct PulseAudioSink(*mut pa_simple); pub struct PulseAudioSink(*mut pa_simple);
impl Open for PulseAudioSink { impl Open for PulseAudioSink {
fn open(device: Option<&str>) -> PulseAudioSink { fn open(device: Option<String>) -> PulseAudioSink {
debug!("Using PulseAudio sink"); debug!("Using PulseAudio sink");
if device.is_some() { if device.is_some() {

View file

@ -6,8 +6,8 @@ use std::thread;
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
use tokio_core::reactor::Handle; use tokio_core::reactor::Handle;
pub struct SinkAdaptor<T>(Option<mpsc::Sender<T>>); pub struct SinkAdaptor<T>(pub Option<mpsc::Sender<T>>);
pub struct StreamAdaptor<T, E>(Option<mpsc::Receiver<Result<T, E>>>); pub struct StreamAdaptor<T, E>(pub Option<mpsc::Receiver<Result<T, E>>>);
impl <T> SinkAdaptor<T> { impl <T> SinkAdaptor<T> {
pub fn send(&mut self, item: T) { pub fn send(&mut self, item: T) {
@ -30,59 +30,33 @@ impl <T, E> StreamAdaptor<T, E> {
} }
} }
fn adapt_sink<S>(sink: S, rx: mpsc::Receiver<S::SinkItem>) -> BoxFuture<(), ()> pub fn adapt<S, E>(transport: S) -> (SinkAdaptor<S::SinkItem>,
where S: Sink + Send + 'static, StreamAdaptor<S::Item, E>,
S::SinkItem: Send, BoxFuture<(), E>)
S::SinkError: Send, where S: Sink<SinkError=E> + Stream<Error=E> + Send + 'static,
{
rx.map_err(|_| -> S::SinkError { panic!("") })
.forward(sink)
.map(|_| ()).map_err(|_| ())
.boxed()
}
fn adapt_stream<S>(stream: S, tx: mpsc::Sender<Result<S::Item, S::Error>>) -> BoxFuture<(), ()>
where S: Stream + Send + 'static,
S::Item: Send,
S::Error: Send,
{
stream.then(ok::<_, mpsc::SendError<_>>)
.forward(tx)
.map(|_| ()).map_err(|_| ())
.boxed()
}
pub fn adapt<F, U, S>(f: F) -> (SinkAdaptor<S::SinkItem>, StreamAdaptor<S::Item, S::Error>)
where F: FnOnce(Handle) -> U + Send + 'static,
U: IntoFuture<Item=S>,
S: Sink + Stream + Send + 'static,
S::Item: Send + 'static, S::Item: Send + 'static,
S::Error: Send + 'static,
S::SinkItem: Send + 'static, S::SinkItem: Send + 'static,
S::SinkError: Send + 'static, E: Send + 'static,
{ {
let (receiver_tx, receiver_rx) = mpsc::channel(0); let (receiver_tx, receiver_rx) = mpsc::channel(0);
let (sender_tx, sender_rx) = mpsc::channel(0); let (sender_tx, sender_rx) = mpsc::channel(0);
let (sink, stream) = transport.split();
thread::spawn(move || { let receiver_task = stream
let mut core = Core::new().unwrap(); .then(ok::<_, mpsc::SendError<_>>)
let handle = core.handle(); .forward(receiver_tx).map(|_| ())
let task = .map_err(|e| -> E { panic!(e) });
f(handle).into_future()
.map(|connection| connection.split())
.map_err(|_| ())
.and_then(|(sink, stream)| {
(adapt_sink(sink, sender_rx),
adapt_stream(stream, receiver_tx))
});
core.run(task).unwrap(); let sender_task = sender_rx
}); .map_err(|e| -> E { panic!(e) })
.forward(sink).map(|_| ());
let task = (receiver_task, sender_task).into_future()
.map(|((), ())| ()).boxed();
(SinkAdaptor(Some(sender_tx)), (SinkAdaptor(Some(sender_tx)),
StreamAdaptor(Some(receiver_rx))) StreamAdaptor(Some(receiver_rx)), task)
} }
pub fn adapt_future<F, U>(f: F) -> oneshot::Receiver<Result<U::Item, U::Error>> pub fn adapt_future<F, U>(f: F) -> oneshot::Receiver<Result<U::Item, U::Error>>

View file

@ -3,6 +3,8 @@ extern crate getopts;
extern crate librespot; extern crate librespot;
extern crate ctrlc; extern crate ctrlc;
extern crate env_logger; extern crate env_logger;
extern crate futures;
extern crate tokio_core;
use env_logger::LogBuilder; use env_logger::LogBuilder;
use std::io::{stderr, Write}; use std::io::{stderr, Write};
@ -11,10 +13,12 @@ use std::thread;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use futures::Future;
use tokio_core::reactor::Core;
use librespot::spirc::SpircManager; use librespot::spirc::SpircManager;
use librespot::authentication::get_credentials; use librespot::authentication::{get_credentials, Credentials};
use librespot::audio_backend::{self, BACKENDS}; use librespot::audio_backend::{self, Sink, BACKENDS};
use librespot::cache::{Cache, DefaultCache, NoCache}; use librespot::cache::{Cache, DefaultCache, NoCache};
use librespot::player::Player; use librespot::player::Player;
use librespot::session::{Bitrate, Config, Session}; use librespot::session::{Bitrate, Config, Session};
@ -59,7 +63,15 @@ fn list_backends() {
} }
} }
fn setup(args: &[String]) -> (Session, Player) { struct Setup {
backend: &'static (Fn(Option<String>) -> Box<Sink> + Send + Sync),
cache: Box<Cache + Send + Sync>,
config: Config,
credentials: Credentials,
device: Option<String>,
}
fn setup(args: &[String]) -> Setup {
let mut opts = getopts::Options::new(); let mut opts = getopts::Options::new();
opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE") opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
.reqopt("n", "name", "Device name", "NAME") .reqopt("n", "name", "Device name", "NAME")
@ -101,8 +113,8 @@ fn setup(args: &[String]) -> (Session, Player) {
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate")) .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
.unwrap_or(Bitrate::Bitrate160); .unwrap_or(Bitrate::Bitrate160);
let device_name = matches.opt_str("name").unwrap(); let name = matches.opt_str("name").unwrap();
let device_id = librespot::session::device_id(&device_name); let device_id = librespot::session::device_id(&name);
let cache = matches.opt_str("c").map(|cache_location| { let cache = matches.opt_str("c").map(|cache_location| {
Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap()) Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap())
@ -111,46 +123,59 @@ fn setup(args: &[String]) -> (Session, Player) {
let cached_credentials = cache.get_credentials(); let cached_credentials = cache.get_credentials();
let credentials = get_credentials(&device_name, &device_id, let credentials = get_credentials(&name, &device_id,
matches.opt_str("username"), matches.opt_str("username"),
matches.opt_str("password"), matches.opt_str("password"),
cached_credentials); cached_credentials);
let config = Config { let config = Config {
user_agent: version::version_string(), user_agent: version::version_string(),
device_name: device_name, name: name,
device_id: device_id, device_id: device_id,
bitrate: bitrate, bitrate: bitrate,
onstart: matches.opt_str("onstart"), onstart: matches.opt_str("onstart"),
onstop: matches.opt_str("onstop"), onstop: matches.opt_str("onstop"),
}; };
let session = Session::new(config, cache); let device = matches.opt_str("device");
session.login(credentials).unwrap(); Setup {
backend: backend,
let device_name = matches.opt_str("device"); cache: cache,
let player = Player::new(session.clone(), move || { config: config,
(backend)(device_name.as_ref().map(AsRef::as_ref)) credentials: credentials,
}); device: device,
}
(session, player)
} }
fn main() { fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let (session, player) = setup(&args);
let Setup { backend, cache, config, credentials, device } = setup(&args);
let connection = Session::connect(config, credentials, cache, handle);
let task = connection.and_then(move |(session, task)| {
let player = Player::new(session.clone(), move || {
(backend)(device)
});
let spirc = SpircManager::new(session.clone(), player); let spirc = SpircManager::new(session.clone(), player);
let spirc_signal = spirc.clone(); let spirc_signal = spirc.clone();
thread::spawn(move || spirc.run());
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
spirc_signal.send_goodbye(); spirc_signal.send_goodbye();
exit(0); exit(0);
}); });
loop { thread::spawn(move || spirc.run());
session.poll(); thread::spawn(move || loop { session.poll() });
}
task
});
core.run(task).unwrap()
} }

View file

@ -9,7 +9,7 @@ use std::sync::{Mutex, RwLock, Arc, mpsc};
use std::str::FromStr; use std::str::FromStr;
use futures::Future as Future_; use futures::Future as Future_;
use futures::Stream; use futures::Stream;
use futures::sync::oneshot; use tokio_core::reactor::Handle;
use album_cover::AlbumCover; use album_cover::AlbumCover;
use apresolve::apresolve_or_fallback; use apresolve::apresolve_or_fallback;
@ -45,7 +45,7 @@ impl FromStr for Bitrate {
pub struct Config { pub struct Config {
pub user_agent: String, pub user_agent: String,
pub device_name: String, pub name: String,
pub device_id: String, pub device_id: String,
pub bitrate: Bitrate, pub bitrate: Bitrate,
pub onstart: Option<String>, pub onstart: Option<String>,
@ -66,45 +66,24 @@ pub struct SessionInternal {
metadata: Mutex<MetadataManager>, metadata: Mutex<MetadataManager>,
stream: Mutex<StreamManager>, stream: Mutex<StreamManager>,
audio_key: Mutex<AudioKeyManager>, audio_key: Mutex<AudioKeyManager>,
rx_connection: Mutex<Option<adaptor::StreamAdaptor<(u8, Vec<u8>), io::Error>>>, rx_connection: Mutex<adaptor::StreamAdaptor<(u8, Vec<u8>), io::Error>>,
tx_connection: Mutex<Option<adaptor::SinkAdaptor<(u8, Vec<u8>)>>>, tx_connection: Mutex<adaptor::SinkAdaptor<(u8, Vec<u8>)>>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Session(pub Arc<SessionInternal>); pub struct Session(pub Arc<SessionInternal>);
pub fn device_id(device_name: &str) -> String { pub fn device_id(name: &str) -> String {
let mut h = Sha1::new(); let mut h = Sha1::new();
h.input_str(&device_name); h.input_str(&name);
h.result_str() h.result_str()
} }
impl Session { impl Session {
pub fn new(config: Config, cache: Box<Cache + Send + Sync>) -> Session { pub fn connect(config: Config, credentials: Credentials,
Session(Arc::new(SessionInternal { cache: Box<Cache + Send + Sync>, handle: Handle)
config: config, -> Box<Future_<Item=(Session, Box<Future_<Item=(), Error=io::Error>>), Error=io::Error>>
data: RwLock::new(SessionData { {
country: String::new(),
canonical_username: String::new(),
}),
rx_connection: Mutex::new(None),
tx_connection: Mutex::new(None),
cache: cache,
mercury: Mutex::new(MercuryManager::new()),
metadata: Mutex::new(MetadataManager::new()),
stream: Mutex::new(StreamManager::new()),
audio_key: Mutex::new(AudioKeyManager::new()),
}))
}
pub fn login(&self, credentials: Credentials) -> Result<Credentials, ()> {
let device_id = self.device_id().to_owned();
let (creds_tx, creds_rx) = oneshot::channel();
let (tx, rx) = adaptor::adapt(move |handle| {
let access_point = apresolve_or_fallback::<io::Error>(&handle); let access_point = apresolve_or_fallback::<io::Error>(&handle);
let connection = access_point.and_then(move |addr| { let connection = access_point.and_then(move |addr| {
@ -112,29 +91,49 @@ impl Session {
connection::connect::<&str>(&addr, &handle) connection::connect::<&str>(&addr, &handle)
}); });
let device_id = config.device_id.clone();
let authentication = connection.and_then(move |connection| { let authentication = connection.and_then(move |connection| {
connection::authenticate(connection, credentials, device_id) connection::authenticate(connection, credentials, device_id)
}); });
authentication.map(|(transport, creds)| { let result = authentication.map(move |(transport, reusable_credentials)| {
creds_tx.complete(creds); info!("Authenticated !");
transport.map(|(cmd, data)| (cmd, data.as_ref().to_owned())) cache.put_credentials(&reusable_credentials);
})
let (session, task) = Session::create(transport, config, cache, reusable_credentials.username.clone());
(session, task)
}); });
let reusable_credentials: Credentials = creds_rx.wait().unwrap(); Box::new(result)
self.0.data.write().unwrap().canonical_username = reusable_credentials.username.clone();
*self.0.rx_connection.lock().unwrap() = Some(rx);
*self.0.tx_connection.lock().unwrap() = Some(tx);
info!("Authenticated !");
self.0.cache.put_credentials(&reusable_credentials);
Ok(reusable_credentials)
} }
fn create(transport: connection::Transport, config: Config,
cache: Box<Cache + Send + Sync>, username: String) -> (Session, Box<Future_<Item=(), Error=io::Error>>)
{
let transport = transport.map(|(cmd, data)| (cmd, data.as_ref().to_owned()));
let (tx, rx, task) = adaptor::adapt(transport);
let session = Session(Arc::new(SessionInternal {
config: config,
data: RwLock::new(SessionData {
country: String::new(),
canonical_username: username,
}),
rx_connection: Mutex::new(rx),
tx_connection: Mutex::new(tx),
cache: cache,
mercury: Mutex::new(MercuryManager::new()),
metadata: Mutex::new(MetadataManager::new()),
stream: Mutex::new(StreamManager::new()),
audio_key: Mutex::new(AudioKeyManager::new()),
}));
(session, task)
}
pub fn poll(&self) { pub fn poll(&self) {
let (cmd, data) = self.recv(); let (cmd, data) = self.recv();
@ -152,11 +151,11 @@ impl Session {
} }
pub fn recv(&self) -> (u8, Vec<u8>) { pub fn recv(&self) -> (u8, Vec<u8>) {
self.0.rx_connection.lock().unwrap().as_mut().unwrap().recv().unwrap() self.0.rx_connection.lock().unwrap().recv().unwrap()
} }
pub fn send_packet(&self, cmd: u8, data: Vec<u8>) { pub fn send_packet(&self, cmd: u8, data: Vec<u8>) {
self.0.tx_connection.lock().unwrap().as_mut().unwrap().send((cmd, data)) self.0.tx_connection.lock().unwrap().send((cmd, data))
} }
pub fn audio_key(&self, track: SpotifyId, file_id: FileId) -> Future<AudioKey, AudioKeyError> { pub fn audio_key(&self, track: SpotifyId, file_id: FileId) -> Future<AudioKey, AudioKeyError> {

View file

@ -46,7 +46,7 @@ struct SpircInternal {
impl SpircManager { impl SpircManager {
pub fn new(session: Session, player: Player) -> SpircManager { pub fn new(session: Session, player: Player) -> SpircManager {
let ident = session.device_id().to_owned(); let ident = session.device_id().to_owned();
let name = session.config().device_name.clone(); let name = session.config().name.clone();
SpircManager(Arc::new(Mutex::new(SpircInternal { SpircManager(Arc::new(Mutex::new(SpircInternal {
player: player, player: player,