mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Update to Rust 2018
- Fix deprecated Error::cause warnings and missing dyn - Reset max_width - Add rustfmt to Travis - Run rustfmt on full codebase with `cargo fmt --all` - Add rustfmt to Travis - Complete migration to edition 2018 - Replace try! shorthand - Use explicit `dyn Trait`
This commit is contained in:
parent
be2ad9059a
commit
d26590afc5
45 changed files with 331 additions and 238 deletions
|
@ -28,12 +28,14 @@ addons:
|
||||||
- libsdl2-dev
|
- libsdl2-dev
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- rustup component add rustfmt
|
||||||
- mkdir -p ~/.cargo
|
- mkdir -p ~/.cargo
|
||||||
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
||||||
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
||||||
- rustup target add armv7-unknown-linux-gnueabihf
|
- rustup target add armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- cargo fmt --all -- --check
|
||||||
- cargo build --locked --no-default-features
|
- cargo build --locked --no-default-features
|
||||||
- cargo build --locked --examples
|
- cargo build --locked --examples
|
||||||
- cargo build --locked --no-default-features --features "with-tremor"
|
- cargo build --locked --no-default-features --features "with-tremor"
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use aes_ctr::Aes128Ctr;
|
|
||||||
use aes_ctr::stream_cipher::{
|
|
||||||
NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek
|
|
||||||
};
|
|
||||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
||||||
|
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
|
||||||
|
use aes_ctr::Aes128Ctr;
|
||||||
|
|
||||||
use librespot_core::audio_key::AudioKey;
|
use librespot_core::audio_key::AudioKey;
|
||||||
|
|
||||||
const AUDIO_AESIV: [u8; 16] = [
|
const AUDIO_AESIV: [u8; 16] = [
|
||||||
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77,
|
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
|
||||||
0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub struct AudioDecrypt<T: io::Read> {
|
pub struct AudioDecrypt<T: io::Read> {
|
||||||
|
@ -30,7 +27,7 @@ impl<T: io::Read> AudioDecrypt<T> {
|
||||||
|
|
||||||
impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
||||||
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
||||||
let len = try!(self.reader.read(output));
|
let len = self.reader.read(output)?;
|
||||||
|
|
||||||
self.cipher.apply_keystream(&mut output[..len]);
|
self.cipher.apply_keystream(&mut output[..len]);
|
||||||
|
|
||||||
|
@ -40,7 +37,7 @@ impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
||||||
|
|
||||||
impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
|
impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
let newpos = try!(self.reader.seek(pos));
|
let newpos = self.reader.seek(pos)?;
|
||||||
|
|
||||||
self.cipher.seek(newpos);
|
self.cipher.seek(newpos);
|
||||||
|
|
||||||
|
|
|
@ -1009,7 +1009,7 @@ impl Read for AudioFileStreaming {
|
||||||
|
|
||||||
self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap();
|
self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap();
|
||||||
let read_len = min(length, available_length);
|
let read_len = min(length, available_length);
|
||||||
let read_len = try!(self.read_file.read(&mut output[..read_len]));
|
let read_len = self.read_file.read(&mut output[..read_len])?;
|
||||||
|
|
||||||
if download_message_printed {
|
if download_message_printed {
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -1031,7 +1031,7 @@ impl Read for AudioFileStreaming {
|
||||||
|
|
||||||
impl Seek for AudioFileStreaming {
|
impl Seek for AudioFileStreaming {
|
||||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||||
self.position = try!(self.read_file.seek(pos));
|
self.position = self.read_file.seek(pos)?;
|
||||||
// Do not seek past EOF
|
// Do not seek past EOF
|
||||||
self.shared
|
self.shared
|
||||||
.read_position
|
.read_position
|
||||||
|
|
|
@ -31,6 +31,6 @@ pub use fetch::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
||||||
pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||||
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl error::Error for VorbisError {
|
||||||
error::Error::description(&self.0)
|
error::Error::description(&self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&error::Error> {
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
error::Error::cause(&self.0)
|
error::Error::source(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::protocol::spirc::TrackRef;
|
||||||
use librespot_core::spotify_id::SpotifyId;
|
use librespot_core::spotify_id::SpotifyId;
|
||||||
use protocol::spirc::TrackRef;
|
|
||||||
|
|
||||||
use serde;
|
use serde;
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ fn deserialize_protobuf_TrackRef<'d, D>(de: D) -> Result<Vec<TrackRef>, D::Error
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'d>,
|
D: serde::Deserializer<'d>,
|
||||||
{
|
{
|
||||||
let v: Vec<TrackContext> = try!(serde::Deserialize::deserialize(de));
|
let v: Vec<TrackContext> = serde::Deserialize::deserialize(de)?;
|
||||||
let track_vec = v
|
let track_vec = v
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use base64;
|
|
||||||
use sha1::{Sha1, Digest};
|
|
||||||
use hmac::{Hmac, Mac};
|
|
||||||
use aes_ctr::Aes128Ctr;
|
|
||||||
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
|
|
||||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
||||||
|
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
|
||||||
|
use aes_ctr::Aes128Ctr;
|
||||||
|
use base64;
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
use futures::{Future, Poll, Stream};
|
use futures::{Future, Poll, Stream};
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
use hyper::server::{Http, Request, Response, Service};
|
use hyper::server::{Http, Request, Response, Service};
|
||||||
use hyper::{self, Get, Post, StatusCode};
|
use hyper::{self, Get, Post, StatusCode};
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
#[cfg(feature = "with-dns-sd")]
|
||||||
use dns_sd::DNSService;
|
use dns_sd::DNSService;
|
||||||
|
@ -114,21 +114,18 @@ impl Discovery {
|
||||||
let base_key = &base_key[..16];
|
let base_key = &base_key[..16];
|
||||||
|
|
||||||
let checksum_key = {
|
let checksum_key = {
|
||||||
let mut h = HmacSha1::new_varkey(base_key)
|
let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
|
||||||
.expect("HMAC can take key of any size");
|
|
||||||
h.input(b"checksum");
|
h.input(b"checksum");
|
||||||
h.result().code()
|
h.result().code()
|
||||||
};
|
};
|
||||||
|
|
||||||
let encryption_key = {
|
let encryption_key = {
|
||||||
let mut h = HmacSha1::new_varkey(&base_key)
|
let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
|
||||||
.expect("HMAC can take key of any size");
|
|
||||||
h.input(b"encryption");
|
h.input(b"encryption");
|
||||||
h.result().code()
|
h.result().code()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut h = HmacSha1::new_varkey(&checksum_key)
|
let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size");
|
||||||
.expect("HMAC can take key of any size");
|
|
||||||
h.input(encrypted);
|
h.input(encrypted);
|
||||||
if let Err(_) = h.verify(cksum) {
|
if let Err(_) = h.verify(cksum) {
|
||||||
warn!("Login error for user {:?}: MAC mismatch", username);
|
warn!("Login error for user {:?}: MAC mismatch", username);
|
||||||
|
@ -139,7 +136,7 @@ impl Discovery {
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = result.to_string();
|
let body = result.to_string();
|
||||||
return ::futures::finished(Response::new().with_body(body))
|
return ::futures::finished(Response::new().with_body(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
let decrypted = {
|
let decrypted = {
|
||||||
|
@ -152,7 +149,8 @@ impl Discovery {
|
||||||
String::from_utf8(data).unwrap()
|
String::from_utf8(data).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
|
let credentials =
|
||||||
|
Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
|
||||||
|
|
||||||
self.0.tx.unbounded_send(credentials).unwrap();
|
self.0.tx.unbounded_send(credentials).unwrap();
|
||||||
|
|
||||||
|
@ -175,7 +173,7 @@ impl Service for Discovery {
|
||||||
type Request = Request;
|
type Request = Request;
|
||||||
type Response = Response;
|
type Response = Response;
|
||||||
type Error = hyper::Error;
|
type Error = hyper::Error;
|
||||||
type Future = Box<Future<Item = Response, Error = hyper::Error>>;
|
type Future = Box<dyn Future<Item = Response, Error = hyper::Error>>;
|
||||||
|
|
||||||
fn call(&self, request: Request) -> Self::Future {
|
fn call(&self, request: Request) -> Self::Future {
|
||||||
let mut params = BTreeMap::new();
|
let mut params = BTreeMap::new();
|
||||||
|
@ -194,17 +192,18 @@ impl Service for Discovery {
|
||||||
body.fold(Vec::new(), |mut acc, chunk| {
|
body.fold(Vec::new(), |mut acc, chunk| {
|
||||||
acc.extend_from_slice(chunk.as_ref());
|
acc.extend_from_slice(chunk.as_ref());
|
||||||
Ok::<_, hyper::Error>(acc)
|
Ok::<_, hyper::Error>(acc)
|
||||||
}).map(move |body| {
|
})
|
||||||
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
.map(move |body| {
|
||||||
params
|
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
||||||
})
|
params
|
||||||
.and_then(
|
})
|
||||||
move |params| match (method, params.get("action").map(AsRef::as_ref)) {
|
.and_then(move |params| {
|
||||||
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
match (method, params.get("action").map(AsRef::as_ref)) {
|
||||||
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
||||||
_ => this.not_found(),
|
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
||||||
},
|
_ => this.not_found(),
|
||||||
),
|
}
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +234,8 @@ pub fn discovery(
|
||||||
&format!("0.0.0.0:{}", port).parse().unwrap(),
|
&format!("0.0.0.0:{}", port).parse().unwrap(),
|
||||||
&handle,
|
&handle,
|
||||||
move || Ok(discovery.clone()),
|
move || Ok(discovery.clone()),
|
||||||
).unwrap()
|
)
|
||||||
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let s_port = serve.incoming_ref().local_addr().port();
|
let s_port = serve.incoming_ref().local_addr().port();
|
||||||
|
@ -260,7 +260,8 @@ pub fn discovery(
|
||||||
None,
|
None,
|
||||||
s_port,
|
s_port,
|
||||||
&["VERSION=1.0", "CPath=/"],
|
&["VERSION=1.0", "CPath=/"],
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
#[cfg(not(feature = "with-dns-sd"))]
|
#[cfg(not(feature = "with-dns-sd"))]
|
||||||
let responder = libmdns::Responder::spawn(&handle)?;
|
let responder = libmdns::Responder::spawn(&handle)?;
|
||||||
|
|
|
@ -15,10 +15,10 @@ extern crate rand;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
|
||||||
extern crate sha1;
|
|
||||||
extern crate hmac;
|
|
||||||
extern crate aes_ctr;
|
extern crate aes_ctr;
|
||||||
extern crate block_modes;
|
extern crate block_modes;
|
||||||
|
extern crate hmac;
|
||||||
|
extern crate sha1;
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
#[cfg(feature = "with-dns-sd")]
|
||||||
extern crate dns_sd;
|
extern crate dns_sd;
|
||||||
|
|
|
@ -9,7 +9,11 @@ use rand;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use context::StationContext;
|
use crate::context::StationContext;
|
||||||
|
use crate::playback::mixer::Mixer;
|
||||||
|
use crate::playback::player::Player;
|
||||||
|
use crate::protocol;
|
||||||
|
use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
|
||||||
use librespot_core::config::ConnectConfig;
|
use librespot_core::config::ConnectConfig;
|
||||||
use librespot_core::mercury::MercuryError;
|
use librespot_core::mercury::MercuryError;
|
||||||
use librespot_core::session::Session;
|
use librespot_core::session::Session;
|
||||||
|
@ -17,14 +21,10 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
|
||||||
use librespot_core::util::SeqGenerator;
|
use librespot_core::util::SeqGenerator;
|
||||||
use librespot_core::version;
|
use librespot_core::version;
|
||||||
use librespot_core::volume::Volume;
|
use librespot_core::volume::Volume;
|
||||||
use playback::mixer::Mixer;
|
|
||||||
use playback::player::Player;
|
|
||||||
use protocol;
|
|
||||||
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
|
|
||||||
|
|
||||||
pub struct SpircTask {
|
pub struct SpircTask {
|
||||||
player: Player,
|
player: Player,
|
||||||
mixer: Box<Mixer>,
|
mixer: Box<dyn Mixer>,
|
||||||
config: SpircTaskConfig,
|
config: SpircTaskConfig,
|
||||||
|
|
||||||
sequence: SeqGenerator<u32>,
|
sequence: SeqGenerator<u32>,
|
||||||
|
@ -33,15 +33,15 @@ pub struct SpircTask {
|
||||||
device: DeviceState,
|
device: DeviceState,
|
||||||
state: State,
|
state: State,
|
||||||
|
|
||||||
subscription: Box<Stream<Item = Frame, Error = MercuryError>>,
|
subscription: Box<dyn Stream<Item = Frame, Error = MercuryError>>,
|
||||||
sender: Box<Sink<SinkItem = Frame, SinkError = MercuryError>>,
|
sender: Box<dyn Sink<SinkItem = Frame, SinkError = MercuryError>>,
|
||||||
commands: mpsc::UnboundedReceiver<SpircCommand>,
|
commands: mpsc::UnboundedReceiver<SpircCommand>,
|
||||||
end_of_track: Box<Future<Item = (), Error = oneshot::Canceled>>,
|
end_of_track: Box<dyn Future<Item = (), Error = oneshot::Canceled>>,
|
||||||
|
|
||||||
shutdown: bool,
|
shutdown: bool,
|
||||||
session: Session,
|
session: Session,
|
||||||
context_fut: Box<Future<Item = serde_json::Value, Error = MercuryError>>,
|
context_fut: Box<dyn Future<Item = serde_json::Value, Error = MercuryError>>,
|
||||||
autoplay_fut: Box<Future<Item = String, Error = MercuryError>>,
|
autoplay_fut: Box<dyn Future<Item = String, Error = MercuryError>>,
|
||||||
context: Option<StationContext>,
|
context: Option<StationContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ impl Spirc {
|
||||||
config: ConnectConfig,
|
config: ConnectConfig,
|
||||||
session: Session,
|
session: Session,
|
||||||
player: Player,
|
player: Player,
|
||||||
mixer: Box<Mixer>,
|
mixer: Box<dyn Mixer>,
|
||||||
) -> (Spirc, SpircTask) {
|
) -> (Spirc, SpircTask) {
|
||||||
debug!("new Spirc[{}]", session.session_id());
|
debug!("new Spirc[{}]", session.session_id());
|
||||||
|
|
||||||
|
@ -526,7 +526,8 @@ impl SpircTask {
|
||||||
|
|
||||||
if self.state.get_track().len() > 0 {
|
if self.state.get_track().len() > 0 {
|
||||||
let now = self.now_ms();
|
let now = self.now_ms();
|
||||||
self.state.set_position_ms(frame.get_state().get_position_ms());
|
self.state
|
||||||
|
.set_position_ms(frame.get_state().get_position_ms());
|
||||||
self.state.set_position_measured_at(now as u64);
|
self.state.set_position_measured_at(now as u64);
|
||||||
|
|
||||||
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
||||||
|
@ -689,7 +690,8 @@ impl SpircTask {
|
||||||
tracks_len - new_index < CONTEXT_FETCH_THRESHOLD
|
tracks_len - new_index < CONTEXT_FETCH_THRESHOLD
|
||||||
);
|
);
|
||||||
let context_uri = self.state.get_context_uri().to_owned();
|
let context_uri = self.state.get_context_uri().to_owned();
|
||||||
if (context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:"))
|
if (context_uri.starts_with("spotify:station:")
|
||||||
|
|| context_uri.starts_with("spotify:dailymix:"))
|
||||||
&& ((self.state.get_track().len() as u32) - new_index) < CONTEXT_FETCH_THRESHOLD
|
&& ((self.state.get_track().len() as u32) - new_index) < CONTEXT_FETCH_THRESHOLD
|
||||||
{
|
{
|
||||||
self.context_fut = self.resolve_station(&context_uri);
|
self.context_fut = self.resolve_station(&context_uri);
|
||||||
|
@ -785,13 +787,16 @@ impl SpircTask {
|
||||||
self.state.get_position_ms() + diff as u32
|
self.state.get_position_ms() + diff as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_station(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
fn resolve_station(
|
||||||
|
&self,
|
||||||
|
uri: &str,
|
||||||
|
) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||||
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
|
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
|
||||||
|
|
||||||
self.resolve_uri(&radio_uri)
|
self.resolve_uri(&radio_uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_autoplay_uri(&self, uri: &str) -> Box<Future<Item = String, Error = MercuryError>> {
|
fn resolve_autoplay_uri(&self, uri: &str) -> Box<dyn Future<Item = String, Error = MercuryError>> {
|
||||||
let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri);
|
let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri);
|
||||||
let request = self.session.mercury().get(query_uri);
|
let request = self.session.mercury().get(query_uri);
|
||||||
Box::new(request.and_then(move |response| {
|
Box::new(request.and_then(move |response| {
|
||||||
|
@ -806,11 +811,14 @@ impl SpircTask {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_uri(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
fn resolve_uri(&self, uri: &str) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||||
let request = self.session.mercury().get(uri);
|
let request = self.session.mercury().get(uri);
|
||||||
|
|
||||||
Box::new(request.and_then(move |response| {
|
Box::new(request.and_then(move |response| {
|
||||||
let data = response.payload.first().expect("Empty payload on context uri");
|
let data = response
|
||||||
|
.payload
|
||||||
|
.first()
|
||||||
|
.expect("Empty payload on context uri");
|
||||||
let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
|
let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
@ -828,7 +836,8 @@ impl SpircTask {
|
||||||
track_vec.drain(0..head);
|
track_vec.drain(0..head);
|
||||||
}
|
}
|
||||||
track_vec.extend_from_slice(&new_tracks);
|
track_vec.extend_from_slice(&new_tracks);
|
||||||
self.state.set_track(protobuf::RepeatedField::from_vec(track_vec));
|
self.state
|
||||||
|
.set_track(protobuf::RepeatedField::from_vec(track_vec));
|
||||||
|
|
||||||
// Update playing index
|
// Update playing index
|
||||||
if let Some(new_index) = self
|
if let Some(new_index) = self
|
||||||
|
@ -849,7 +858,9 @@ impl SpircTask {
|
||||||
let context_uri = frame.get_state().get_context_uri().to_owned();
|
let context_uri = frame.get_state().get_context_uri().to_owned();
|
||||||
let tracks = frame.get_state().get_track();
|
let tracks = frame.get_state().get_track();
|
||||||
debug!("Frame has {:?} tracks", tracks.len());
|
debug!("Frame has {:?} tracks", tracks.len());
|
||||||
if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") {
|
if context_uri.starts_with("spotify:station:")
|
||||||
|
|| context_uri.starts_with("spotify:dailymix:")
|
||||||
|
{
|
||||||
self.context_fut = self.resolve_station(&context_uri);
|
self.context_fut = self.resolve_station(&context_uri);
|
||||||
} else if self.config.autoplay {
|
} else if self.config.autoplay {
|
||||||
info!("Fetching autoplay context uri");
|
info!("Fetching autoplay context uri");
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::str::FromStr;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
error_chain!{}
|
error_chain! {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct APResolveData {
|
pub struct APResolveData {
|
||||||
|
@ -21,7 +21,7 @@ fn apresolve(
|
||||||
handle: &Handle,
|
handle: &Handle,
|
||||||
proxy: &Option<Url>,
|
proxy: &Option<Url>,
|
||||||
ap_port: &Option<u16>,
|
ap_port: &Option<u16>,
|
||||||
) -> Box<Future<Item = String, Error = Error>> {
|
) -> Box<dyn Future<Item = String, Error = Error>> {
|
||||||
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
||||||
let use_proxy = proxy.is_some();
|
let use_proxy = proxy.is_some();
|
||||||
|
|
||||||
|
@ -52,19 +52,20 @@ fn apresolve(
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let body = body.then(|result| result.chain_err(|| "HTTP error"));
|
let body = body.then(|result| result.chain_err(|| "HTTP error"));
|
||||||
let body = body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
|
let body =
|
||||||
|
body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
|
||||||
|
|
||||||
let data =
|
let data = body
|
||||||
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
||||||
|
|
||||||
let p = ap_port.clone();
|
let p = ap_port.clone();
|
||||||
|
|
||||||
let ap = data.and_then(move |data| {
|
let ap = data.and_then(move |data| {
|
||||||
let mut aps = data.ap_list.iter().filter(|ap| {
|
let mut aps = data.ap_list.iter().filter(|ap| {
|
||||||
if p.is_some() {
|
if p.is_some() {
|
||||||
Uri::from_str(ap)
|
Uri::from_str(ap).ok().map_or(false, |uri| {
|
||||||
.ok()
|
uri.port().map_or(false, |port| port == p.unwrap())
|
||||||
.map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap()))
|
})
|
||||||
} else if use_proxy {
|
} else if use_proxy {
|
||||||
// It is unlikely that the proxy will accept CONNECT on anything other than 443.
|
// It is unlikely that the proxy will accept CONNECT on anything other than 443.
|
||||||
Uri::from_str(ap)
|
Uri::from_str(ap)
|
||||||
|
@ -86,7 +87,7 @@ pub(crate) fn apresolve_or_fallback<E>(
|
||||||
handle: &Handle,
|
handle: &Handle,
|
||||||
proxy: &Option<Url>,
|
proxy: &Option<Url>,
|
||||||
ap_port: &Option<u16>,
|
ap_port: &Option<u16>,
|
||||||
) -> Box<Future<Item = String, Error = E>>
|
) -> Box<dyn Future<Item = String, Error = E>>
|
||||||
where
|
where
|
||||||
E: 'static,
|
E: 'static,
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,8 @@ use futures::{Async, Future, Poll};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use spotify_id::{FileId, SpotifyId};
|
use crate::spotify_id::{FileId, SpotifyId};
|
||||||
use util::SeqGenerator;
|
use crate::util::SeqGenerator;
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct AudioKey(pub [u8; 16]);
|
pub struct AudioKey(pub [u8; 16]);
|
||||||
|
@ -35,7 +35,11 @@ impl AudioKeyManager {
|
||||||
let _ = sender.send(Ok(AudioKey(key)));
|
let _ = sender.send(Ok(AudioKey(key)));
|
||||||
}
|
}
|
||||||
0xe => {
|
0xe => {
|
||||||
warn!("error audio key {:x} {:x}", data.as_ref()[0], data.as_ref()[1]);
|
warn!(
|
||||||
|
"error audio key {:x} {:x}",
|
||||||
|
data.as_ref()[0],
|
||||||
|
data.as_ref()[1]
|
||||||
|
);
|
||||||
let _ = sender.send(Err(AudioKeyError));
|
let _ = sender.send(Err(AudioKeyError));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
|
use aes::Aes192;
|
||||||
use base64;
|
use base64;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use aes::Aes192;
|
|
||||||
use hmac::Hmac;
|
use hmac::Hmac;
|
||||||
use sha1::{Sha1, Digest};
|
|
||||||
use pbkdf2::pbkdf2;
|
use pbkdf2::pbkdf2;
|
||||||
use protobuf::ProtobufEnum;
|
use protobuf::ProtobufEnum;
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::ops::FnOnce;
|
use std::ops::FnOnce;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use protocol::authentication::AuthenticationType;
|
use crate::protocol::authentication::AuthenticationType;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
|
@ -40,24 +40,24 @@ impl Credentials {
|
||||||
pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials {
|
pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials {
|
||||||
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
|
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
try!(stream.read_exact(&mut data));
|
stream.read_exact(&mut data)?;
|
||||||
Ok(data[0])
|
Ok(data[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
|
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
|
||||||
let lo = try!(read_u8(stream)) as u32;
|
let lo = read_u8(stream)? as u32;
|
||||||
if lo & 0x80 == 0 {
|
if lo & 0x80 == 0 {
|
||||||
return Ok(lo);
|
return Ok(lo);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hi = try!(read_u8(stream)) as u32;
|
let hi = read_u8(stream)? as u32;
|
||||||
Ok(lo & 0x7f | hi << 7)
|
Ok(lo & 0x7f | hi << 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
|
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
|
||||||
let length = try!(read_int(stream));
|
let length = read_int(stream)?;
|
||||||
let mut data = vec![0u8; length as usize];
|
let mut data = vec![0u8; length as usize];
|
||||||
try!(stream.read_exact(&mut data));
|
stream.read_exact(&mut data)?;
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
@ -76,9 +76,9 @@ impl Credentials {
|
||||||
|
|
||||||
// decrypt data using ECB mode without padding
|
// decrypt data using ECB mode without padding
|
||||||
let blob = {
|
let blob = {
|
||||||
use aes::block_cipher_trait::BlockCipher;
|
|
||||||
use aes::block_cipher_trait::generic_array::GenericArray;
|
|
||||||
use aes::block_cipher_trait::generic_array::typenum::Unsigned;
|
use aes::block_cipher_trait::generic_array::typenum::Unsigned;
|
||||||
|
use aes::block_cipher_trait::generic_array::GenericArray;
|
||||||
|
use aes::block_cipher_trait::BlockCipher;
|
||||||
|
|
||||||
let mut data = base64::decode(encrypted_blob).unwrap();
|
let mut data = base64::decode(encrypted_blob).unwrap();
|
||||||
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
||||||
|
@ -148,7 +148,7 @@ where
|
||||||
T: ProtobufEnum,
|
T: ProtobufEnum,
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let v: i32 = try!(serde::Deserialize::deserialize(de));
|
let v: i32 = serde::Deserialize::deserialize(de)?;
|
||||||
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let v: String = try!(serde::Deserialize::deserialize(de));
|
let v: String = serde::Deserialize::deserialize(de)?;
|
||||||
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,9 +181,10 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
|
||||||
Some(credentials.clone())
|
Some(credentials.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(username), None, _) => {
|
(Some(username), None, _) => Some(Credentials::with_password(
|
||||||
Some(Credentials::with_password(username.clone(), prompt(&username)))
|
username.clone(),
|
||||||
}
|
prompt(&username),
|
||||||
|
)),
|
||||||
|
|
||||||
(None, _, Some(credentials)) => Some(credentials),
|
(None, _, Some(credentials)) => Some(credentials),
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use authentication::Credentials;
|
use crate::authentication::Credentials;
|
||||||
use spotify_id::FileId;
|
use crate::spotify_id::FileId;
|
||||||
use volume::Volume;
|
use crate::volume::Volume;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
|
@ -80,7 +80,7 @@ impl Cache {
|
||||||
File::open(self.file_path(file)).ok()
|
File::open(self.file_path(file)).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file(&self, file: FileId, contents: &mut Read) {
|
pub fn save_file(&self, file: FileId, contents: &mut dyn Read) {
|
||||||
if self.use_audio_cache {
|
if self.use_audio_cache {
|
||||||
let path = self.file_path(file);
|
let path = self.file_path(file);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use futures::{Async, Poll, Stream};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use util::SeqGenerator;
|
use crate::util::SeqGenerator;
|
||||||
|
|
||||||
component! {
|
component! {
|
||||||
ChannelManager : ChannelManagerInner {
|
ChannelManager : ChannelManagerInner {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use version;
|
use crate::version;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SessionConfig {
|
pub struct SessionConfig {
|
||||||
|
|
|
@ -88,7 +88,8 @@ impl Decoder for APCodec {
|
||||||
|
|
||||||
let mut payload = buf.split_to(size + MAC_SIZE);
|
let mut payload = buf.split_to(size + MAC_SIZE);
|
||||||
|
|
||||||
self.decode_cipher.decrypt(&mut payload.get_mut(..size).unwrap());
|
self.decode_cipher
|
||||||
|
.decrypt(&mut payload.get_mut(..size).unwrap());
|
||||||
let mac = payload.split_off(size);
|
let mac = payload.split_off(size);
|
||||||
self.decode_cipher.check_mac(mac.as_ref())?;
|
self.decode_cipher.check_mac(mac.as_ref())?;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||||
use hmac::{Hmac, Mac};
|
|
||||||
use sha1::Sha1;
|
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
use sha1::Sha1;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use tokio_codec::{Decoder, Framed};
|
use tokio_codec::{Decoder, Framed};
|
||||||
|
@ -11,10 +11,10 @@ use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use super::codec::APCodec;
|
use super::codec::APCodec;
|
||||||
use diffie_hellman::DHLocalKeys;
|
use crate::diffie_hellman::DHLocalKeys;
|
||||||
use protocol;
|
use crate::protocol;
|
||||||
use protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
|
use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
|
||||||
use util;
|
use crate::util;
|
||||||
|
|
||||||
pub struct Handshake<T> {
|
pub struct Handshake<T> {
|
||||||
keys: DHLocalKeys,
|
keys: DHLocalKeys,
|
||||||
|
@ -62,7 +62,8 @@ impl<T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let shared_secret = self.keys.shared_secret(&remote_key);
|
let shared_secret = self.keys.shared_secret(&remote_key);
|
||||||
let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator);
|
let (challenge, send_key, recv_key) =
|
||||||
|
compute_keys(&shared_secret, &accumulator);
|
||||||
let codec = APCodec::new(&send_key, &recv_key);
|
let codec = APCodec::new(&send_key, &recv_key);
|
||||||
|
|
||||||
let write = client_response(connection, challenge);
|
let write = client_response(connection, challenge);
|
||||||
|
@ -92,7 +93,10 @@ fn client_hello<T: AsyncWrite>(connection: T, gc: Vec<u8>) -> WriteAll<T, Vec<u8
|
||||||
packet
|
packet
|
||||||
.mut_cryptosuites_supported()
|
.mut_cryptosuites_supported()
|
||||||
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON);
|
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON);
|
||||||
packet.mut_login_crypto_hello().mut_diffie_hellman().set_gc(gc);
|
packet
|
||||||
|
.mut_login_crypto_hello()
|
||||||
|
.mut_diffie_hellman()
|
||||||
|
.set_gc(gc);
|
||||||
packet
|
packet
|
||||||
.mut_login_crypto_hello()
|
.mut_login_crypto_hello()
|
||||||
.mut_diffie_hellman()
|
.mut_diffie_hellman()
|
||||||
|
@ -190,15 +194,13 @@ fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(0x64);
|
let mut data = Vec::with_capacity(0x64);
|
||||||
for i in 1..6 {
|
for i in 1..6 {
|
||||||
let mut mac = HmacSha1::new_varkey(&shared_secret)
|
let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size");
|
||||||
.expect("HMAC can take key of any size");
|
|
||||||
mac.input(packets);
|
mac.input(packets);
|
||||||
mac.input(&[i]);
|
mac.input(&[i]);
|
||||||
data.extend_from_slice(&mac.result().code());
|
data.extend_from_slice(&mac.result().code());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mac = HmacSha1::new_varkey(&data[..0x14])
|
let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size");
|
||||||
.expect("HMAC can take key of any size");;
|
|
||||||
mac.input(packets);
|
mac.input(packets);
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -8,15 +8,15 @@ use futures::{Future, Sink, Stream};
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
|
use tokio_codec::Framed;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
use tokio_codec::Framed;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use authentication::Credentials;
|
use crate::authentication::Credentials;
|
||||||
use version;
|
use crate::version;
|
||||||
|
|
||||||
use proxytunnel;
|
use crate::proxytunnel;
|
||||||
|
|
||||||
pub type Transport = Framed<TcpStream, APCodec>;
|
pub type Transport = Framed<TcpStream, APCodec>;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub fn connect(
|
||||||
addr: String,
|
addr: String,
|
||||||
handle: &Handle,
|
handle: &Handle,
|
||||||
proxy: &Option<Url>,
|
proxy: &Option<Url>,
|
||||||
) -> Box<Future<Item = Transport, Error = io::Error>> {
|
) -> Box<dyn Future<Item = Transport, Error = io::Error>> {
|
||||||
let (addr, connect_url) = match *proxy {
|
let (addr, connect_url) = match *proxy {
|
||||||
Some(ref url) => {
|
Some(ref url) => {
|
||||||
info!("Using proxy \"{}\"", url);
|
info!("Using proxy \"{}\"", url);
|
||||||
|
@ -48,23 +48,31 @@ pub fn authenticate(
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
device_id: String,
|
device_id: String,
|
||||||
) -> Box<Future<Item = (Transport, Credentials), Error = io::Error>> {
|
) -> Box<dyn Future<Item = (Transport, Credentials), Error = io::Error>> {
|
||||||
use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||||
use protocol::keyexchange::APLoginFailed;
|
use crate::protocol::keyexchange::APLoginFailed;
|
||||||
|
|
||||||
let mut packet = ClientResponseEncrypted::new();
|
let mut packet = ClientResponseEncrypted::new();
|
||||||
packet.mut_login_credentials().set_username(credentials.username);
|
packet
|
||||||
packet.mut_login_credentials().set_typ(credentials.auth_type);
|
.mut_login_credentials()
|
||||||
|
.set_username(credentials.username);
|
||||||
|
packet
|
||||||
|
.mut_login_credentials()
|
||||||
|
.set_typ(credentials.auth_type);
|
||||||
packet
|
packet
|
||||||
.mut_login_credentials()
|
.mut_login_credentials()
|
||||||
.set_auth_data(credentials.auth_data);
|
.set_auth_data(credentials.auth_data);
|
||||||
packet.mut_system_info().set_cpu_family(CpuFamily::CPU_UNKNOWN);
|
packet
|
||||||
|
.mut_system_info()
|
||||||
|
.set_cpu_family(CpuFamily::CPU_UNKNOWN);
|
||||||
packet.mut_system_info().set_os(Os::OS_UNKNOWN);
|
packet.mut_system_info().set_os(Os::OS_UNKNOWN);
|
||||||
packet.mut_system_info().set_system_information_string(format!(
|
packet
|
||||||
"librespot_{}_{}",
|
.mut_system_info()
|
||||||
version::short_sha(),
|
.set_system_information_string(format!(
|
||||||
version::build_id()
|
"librespot_{}_{}",
|
||||||
));
|
version::short_sha(),
|
||||||
|
version::build_id()
|
||||||
|
));
|
||||||
packet.mut_system_info().set_device_id(device_id);
|
packet.mut_system_info().set_device_id(device_id);
|
||||||
packet.set_version_string(version::version_string());
|
packet.set_version_string(version::version_string());
|
||||||
|
|
||||||
|
@ -77,7 +85,8 @@ pub fn authenticate(
|
||||||
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
||||||
.and_then(|(packet, transport)| match packet {
|
.and_then(|(packet, transport)| match packet {
|
||||||
Some((0xac, data)) => {
|
Some((0xac, data)) => {
|
||||||
let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
let welcome_data: APWelcome =
|
||||||
|
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||||
|
|
||||||
let reusable_credentials = Credentials {
|
let reusable_credentials = Credentials {
|
||||||
username: welcome_data.get_canonical_username().to_owned(),
|
username: welcome_data.get_canonical_username().to_owned(),
|
||||||
|
@ -89,7 +98,8 @@ pub fn authenticate(
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((0xad, data)) => {
|
Some((0xad, data)) => {
|
||||||
let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
let error_data: APLoginFailed =
|
||||||
|
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||||
panic!(
|
panic!(
|
||||||
"Authentication failed with reason: {:?}",
|
"Authentication failed with reason: {:?}",
|
||||||
error_data.get_error_code()
|
error_data.get_error_code()
|
||||||
|
|
|
@ -2,17 +2,18 @@ use num_bigint::BigUint;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use util;
|
use crate::util;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap();
|
pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap();
|
||||||
pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[
|
pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2,
|
||||||
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,
|
0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
|
||||||
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
|
||||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37,
|
0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
|
||||||
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
|
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5,
|
||||||
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +40,11 @@ impl DHLocalKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
||||||
let shared_key = util::powm(&BigUint::from_bytes_be(remote_key), &self.private_key, &DH_PRIME);
|
let shared_key = util::powm(
|
||||||
|
&BigUint::from_bytes_be(remote_key),
|
||||||
|
&self.private_key,
|
||||||
|
&DH_PRIME,
|
||||||
|
);
|
||||||
shared_key.to_bytes_be()
|
shared_key.to_bytes_be()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use mercury::MercuryError;
|
use crate::mercury::MercuryError;
|
||||||
use session::Session;
|
use crate::session::Session;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -17,7 +17,7 @@ pub fn get_token(
|
||||||
session: &Session,
|
session: &Session,
|
||||||
client_id: &str,
|
client_id: &str,
|
||||||
scopes: &str,
|
scopes: &str,
|
||||||
) -> Box<Future<Item = Token, Error = MercuryError>> {
|
) -> Box<dyn Future<Item = Token, Error = MercuryError>> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"hm://keymaster/token/authenticated?client_id={}&scope={}",
|
"hm://keymaster/token/authenticated?client_id={}&scope={}",
|
||||||
client_id, scopes
|
client_id, scopes
|
||||||
|
|
|
@ -11,29 +11,29 @@ extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
extern crate aes;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
|
extern crate hmac;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate hyper_proxy;
|
extern crate hyper_proxy;
|
||||||
extern crate num_bigint;
|
extern crate num_bigint;
|
||||||
extern crate num_integer;
|
extern crate num_integer;
|
||||||
extern crate num_traits;
|
extern crate num_traits;
|
||||||
|
extern crate pbkdf2;
|
||||||
extern crate protobuf;
|
extern crate protobuf;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate sha1;
|
||||||
extern crate shannon;
|
extern crate shannon;
|
||||||
extern crate tokio_codec;
|
extern crate tokio_codec;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
extern crate sha1;
|
|
||||||
extern crate hmac;
|
|
||||||
extern crate pbkdf2;
|
|
||||||
extern crate aes;
|
|
||||||
|
|
||||||
extern crate librespot_protocol as protocol;
|
extern crate librespot_protocol as protocol;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
use crate::protocol;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::sync::{mpsc, oneshot};
|
use futures::sync::{mpsc, oneshot};
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
use protobuf;
|
use protobuf;
|
||||||
use protocol;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use util::SeqGenerator;
|
use crate::util::SeqGenerator;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use self::types::*;
|
pub use self::types::*;
|
||||||
|
@ -95,7 +95,8 @@ impl MercuryManager {
|
||||||
pub fn subscribe<T: Into<String>>(
|
pub fn subscribe<T: Into<String>>(
|
||||||
&self,
|
&self,
|
||||||
uri: T,
|
uri: T,
|
||||||
) -> Box<Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>> {
|
) -> Box<dyn Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>>
|
||||||
|
{
|
||||||
let uri = uri.into();
|
let uri = uri.into();
|
||||||
let request = self.request(MercuryRequest {
|
let request = self.request(MercuryRequest {
|
||||||
method: MercuryMethod::SUB,
|
method: MercuryMethod::SUB,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt};
|
||||||
use protobuf::Message;
|
use protobuf::Message;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use protocol;
|
use crate::protocol;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum MercuryMethod {
|
pub enum MercuryMethod {
|
||||||
|
@ -37,7 +37,8 @@ impl ToString for MercuryMethod {
|
||||||
MercuryMethod::SUB => "SUB",
|
MercuryMethod::SUB => "SUB",
|
||||||
MercuryMethod::UNSUB => "UNSUB",
|
MercuryMethod::UNSUB => "UNSUB",
|
||||||
MercuryMethod::SEND => "SEND",
|
MercuryMethod::SEND => "SEND",
|
||||||
}.to_owned()
|
}
|
||||||
|
.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,9 @@ impl<T: AsyncRead + AsyncWrite> Future for ProxyTunnel<T> {
|
||||||
let mut response = httparse::Response::new(&mut headers);
|
let mut response = httparse::Response::new(&mut headers);
|
||||||
let status = match response.parse(&buf) {
|
let status = match response.parse(&buf) {
|
||||||
Ok(status) => status,
|
Ok(status) => status,
|
||||||
Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err.description())),
|
Err(err) => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, err.description()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if status.is_complete() {
|
if status.is_complete() {
|
||||||
|
@ -102,7 +104,8 @@ fn proxy_connect<T: AsyncWrite>(connection: T, connect_url: &str) -> WriteAll<T,
|
||||||
\r\n",
|
\r\n",
|
||||||
uri.host().expect(&format!("No host in {}", uri)),
|
uri.host().expect(&format!("No host in {}", uri)),
|
||||||
uri.port().expect(&format!("No port in {}", uri))
|
uri.port().expect(&format!("No port in {}", uri))
|
||||||
).into_bytes();
|
)
|
||||||
|
.into_bytes();
|
||||||
|
|
||||||
write_all(connection, buffer)
|
write_all(connection, buffer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::{Arc, RwLock, Weak};
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, RwLock, Weak};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
|
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||||
use tokio_core::reactor::{Handle, Remote};
|
use tokio_core::reactor::{Handle, Remote};
|
||||||
|
|
||||||
use apresolve::apresolve_or_fallback;
|
use crate::apresolve::apresolve_or_fallback;
|
||||||
use audio_key::AudioKeyManager;
|
use crate::audio_key::AudioKeyManager;
|
||||||
use authentication::Credentials;
|
use crate::authentication::Credentials;
|
||||||
use cache::Cache;
|
use crate::cache::Cache;
|
||||||
use channel::ChannelManager;
|
use crate::channel::ChannelManager;
|
||||||
use component::Lazy;
|
use crate::component::Lazy;
|
||||||
use config::SessionConfig;
|
use crate::config::SessionConfig;
|
||||||
use connection;
|
use crate::connection;
|
||||||
use mercury::MercuryManager;
|
use crate::mercury::MercuryManager;
|
||||||
|
|
||||||
struct SessionData {
|
struct SessionData {
|
||||||
country: String,
|
country: String,
|
||||||
|
@ -53,8 +53,9 @@ impl Session {
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
) -> Box<Future<Item = Session, Error = io::Error>> {
|
) -> Box<dyn Future<Item = Session, Error = io::Error>> {
|
||||||
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
let access_point =
|
||||||
|
apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
||||||
|
|
||||||
let handle_ = handle.clone();
|
let handle_ = handle.clone();
|
||||||
let proxy = config.proxy.clone();
|
let proxy = config.proxy.clone();
|
||||||
|
@ -64,8 +65,9 @@ impl Session {
|
||||||
});
|
});
|
||||||
|
|
||||||
let device_id = config.device_id.clone();
|
let device_id = config.device_id.clone();
|
||||||
let authentication = connection
|
let authentication = connection.and_then(move |connection| {
|
||||||
.and_then(move |connection| connection::authenticate(connection, credentials, device_id));
|
connection::authenticate(connection, credentials, device_id)
|
||||||
|
});
|
||||||
|
|
||||||
let result = authentication.map(move |(transport, reusable_credentials)| {
|
let result = authentication.map(move |(transport, reusable_credentials)| {
|
||||||
info!("Authenticated as \"{}\" !", reusable_credentials.username);
|
info!("Authenticated as \"{}\" !", reusable_credentials.username);
|
||||||
|
@ -97,7 +99,7 @@ impl Session {
|
||||||
config: SessionConfig,
|
config: SessionConfig,
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
username: String,
|
username: String,
|
||||||
) -> (Session, Box<Future<Item = (), Error = io::Error>>) {
|
) -> (Session, Box<dyn Future<Item = (), Error = io::Error>>) {
|
||||||
let (sink, stream) = transport.split();
|
let (sink, stream) = transport.split();
|
||||||
|
|
||||||
let (sender_tx, sender_rx) = mpsc::unbounded();
|
let (sender_tx, sender_rx) = mpsc::unbounded();
|
||||||
|
@ -133,7 +135,11 @@ impl Session {
|
||||||
.map(|_| ());
|
.map(|_| ());
|
||||||
let receiver_task = DispatchTask(stream, session.weak());
|
let receiver_task = DispatchTask(stream, session.weak());
|
||||||
|
|
||||||
let task = Box::new((receiver_task, sender_task).into_future().map(|((), ())| ()));
|
let task = Box::new(
|
||||||
|
(receiver_task, sender_task)
|
||||||
|
.into_future()
|
||||||
|
.map(|((), ())| ()),
|
||||||
|
);
|
||||||
|
|
||||||
(session, task)
|
(session, task)
|
||||||
}
|
}
|
||||||
|
@ -197,7 +203,7 @@ impl Session {
|
||||||
|
|
||||||
0x9 | 0xa => self.channel().dispatch(cmd, data),
|
0x9 | 0xa => self.channel().dispatch(cmd, data),
|
||||||
0xd | 0xe => self.audio_key().dispatch(cmd, data),
|
0xd | 0xe => self.audio_key().dispatch(cmd, data),
|
||||||
0xb2...0xb6 => self.mercury().dispatch(cmd, data),
|
0xb2..=0xb6 => self.mercury().dispatch(cmd, data),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,7 +295,8 @@ where
|
||||||
session.shutdown();
|
session.shutdown();
|
||||||
return Err(From::from(e));
|
return Err(From::from(e));
|
||||||
}
|
}
|
||||||
}.expect("connection closed");
|
}
|
||||||
|
.expect("connection closed");
|
||||||
|
|
||||||
session.dispatch(cmd, data);
|
session.dispatch(cmd, data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ pub struct SpotifyId {
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct SpotifyIdError;
|
pub struct SpotifyIdError;
|
||||||
|
|
||||||
const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const BASE62_DIGITS: &'static [u8] =
|
||||||
|
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
||||||
|
|
||||||
impl SpotifyId {
|
impl SpotifyId {
|
||||||
|
|
|
@ -6,7 +6,10 @@ use std::mem;
|
||||||
use std::ops::{Mul, Rem, Shr};
|
use std::ops::{Mul, Rem, Shr};
|
||||||
|
|
||||||
pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> {
|
pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> {
|
||||||
::std::iter::repeat(()).map(|()| rng.gen()).take(size).collect()
|
::std::iter::repeat(())
|
||||||
|
.map(|()| rng.gen())
|
||||||
|
.take(size)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
|
pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
|
||||||
|
|
|
@ -21,7 +21,9 @@ impl Volume {
|
||||||
|
|
||||||
// write volume to file
|
// write volume to file
|
||||||
fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
||||||
writer.write_all(self.volume.to_string().as_bytes()).unwrap();
|
writer
|
||||||
|
.write_all(self.volume.to_string().as_bytes())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
||||||
|
|
|
@ -34,7 +34,9 @@ fn main() {
|
||||||
.run(Session::connect(session_config, credentials, None, handle))
|
.run(Session::connect(session_config, credentials, None, handle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None));
|
let (player, _) = Player::new(player_config, session.clone(), None, move || {
|
||||||
|
(backend)(None)
|
||||||
|
});
|
||||||
|
|
||||||
println!("Playing...");
|
println!("Playing...");
|
||||||
core.run(player.load(track, true, 0)).unwrap();
|
core.run(player.load(track, true, 0)).unwrap();
|
||||||
|
|
|
@ -19,7 +19,7 @@ use librespot_core::mercury::MercuryError;
|
||||||
use librespot_core::session::Session;
|
use librespot_core::session::Session;
|
||||||
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
|
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
|
||||||
|
|
||||||
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
pub use crate::protocol::metadata::AudioFile_Format as FileFormat;
|
||||||
|
|
||||||
fn countrylist_contains(list: &str, country: &str) -> bool {
|
fn countrylist_contains(list: &str, country: &str) -> bool {
|
||||||
list.chunks(2).any(|cc| cc == country)
|
list.chunks(2).any(|cc| cc == country)
|
||||||
|
|
|
@ -64,7 +64,8 @@ impl Open for AlsaSink {
|
||||||
}
|
}
|
||||||
Some(device) => device,
|
Some(device) => device,
|
||||||
None => "default",
|
None => "default",
|
||||||
}.to_string();
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
AlsaSink(None, name)
|
AlsaSink(None, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
use jack::prelude::{
|
use jack::prelude::{
|
||||||
client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler,
|
client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port,
|
||||||
ProcessScope,
|
ProcessHandler, ProcessScope,
|
||||||
};
|
};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
|
@ -45,9 +45,14 @@ impl Open for JackSink {
|
||||||
info!("Using jack sink!");
|
info!("Using jack sink!");
|
||||||
|
|
||||||
let client_name = client_name.unwrap_or("librespot".to_string());
|
let client_name = client_name.unwrap_or("librespot".to_string());
|
||||||
let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
|
let (client, _status) =
|
||||||
let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap();
|
Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
|
||||||
let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap();
|
let ch_r = client
|
||||||
|
.register_port("out_0", AudioOutSpec::default())
|
||||||
|
.unwrap();
|
||||||
|
let ch_l = client
|
||||||
|
.register_port("out_1", AudioOutSpec::default())
|
||||||
|
.unwrap();
|
||||||
// buffer for samples from librespot (~10ms)
|
// buffer for samples from librespot (~10ms)
|
||||||
let (tx, rx) = sync_channel(2 * 1024 * 4);
|
let (tx, rx) = sync_channel(2 * 1024 * 4);
|
||||||
let jack_data = JackData {
|
let jack_data = JackData {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub trait Open {
|
pub trait Open {
|
||||||
fn open(Option<String>) -> Self;
|
fn open(_: Option<String>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Sink {
|
pub trait Sink {
|
||||||
|
@ -10,7 +10,7 @@ pub trait Sink {
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()>;
|
fn write(&mut self, data: &[i16]) -> io::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<Sink> {
|
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<dyn Sink> {
|
||||||
Box::new(S::open(device))
|
Box::new(S::open(device))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ use self::sdl::SdlSink;
|
||||||
mod pipe;
|
mod pipe;
|
||||||
use self::pipe::StdoutSink;
|
use self::pipe::StdoutSink;
|
||||||
|
|
||||||
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] = &[
|
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<dyn Sink>)] = &[
|
||||||
#[cfg(feature = "alsa-backend")]
|
#[cfg(feature = "alsa-backend")]
|
||||||
("alsa", mk_sink::<AlsaSink>),
|
("alsa", mk_sink::<AlsaSink>),
|
||||||
#[cfg(feature = "portaudio-backend")]
|
#[cfg(feature = "portaudio-backend")]
|
||||||
|
@ -62,7 +62,7 @@ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] =
|
||||||
("pipe", mk_sink::<StdoutSink>),
|
("pipe", mk_sink::<StdoutSink>),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> {
|
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<dyn Sink>> {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
BACKENDS
|
BACKENDS
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::io::{self, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
pub struct StdoutSink(Box<Write>);
|
pub struct StdoutSink(Box<dyn Write>);
|
||||||
|
|
||||||
impl Open for StdoutSink {
|
impl Open for StdoutSink {
|
||||||
fn open(path: Option<String>) -> StdoutSink {
|
fn open(path: Option<String>) -> StdoutSink {
|
||||||
|
@ -28,7 +28,10 @@ impl Sink for StdoutSink {
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||||
let data: &[u8] = unsafe {
|
let data: &[u8] = unsafe {
|
||||||
slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<i16>())
|
slice::from_raw_parts(
|
||||||
|
data.as_ptr() as *const u8,
|
||||||
|
data.len() * mem::size_of::<i16>(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.0.write_all(data)?;
|
self.0.write_all(data)?;
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub struct PortAudioSink<'a>(
|
||||||
StreamParameters<i16>,
|
StreamParameters<i16>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn output_devices() -> Box<Iterator<Item = (DeviceIndex, DeviceInfo)>> {
|
fn output_devices() -> Box<dyn Iterator<Item = (DeviceIndex, DeviceInfo)>> {
|
||||||
let count = portaudio_rs::device::get_count().unwrap();
|
let count = portaudio_rs::device::get_count().unwrap();
|
||||||
let devices = (0..count)
|
let devices = (0..count)
|
||||||
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
|
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
|
||||||
|
@ -51,7 +51,8 @@ impl<'a> Open for PortAudioSink<'a> {
|
||||||
}
|
}
|
||||||
Some(device) => find_output(device),
|
Some(device) => find_output(device),
|
||||||
None => get_default_output_index(),
|
None => get_default_output_index(),
|
||||||
}.expect("Could not find device");
|
}
|
||||||
|
.expect("Could not find device");
|
||||||
|
|
||||||
let info = portaudio_rs::device::get_info(device_idx);
|
let info = portaudio_rs::device::get_info(device_idx);
|
||||||
let latency = match info {
|
let latency = match info {
|
||||||
|
@ -81,8 +82,9 @@ impl<'a> Sink for PortAudioSink<'a> {
|
||||||
FRAMES_PER_BUFFER_UNSPECIFIED,
|
FRAMES_PER_BUFFER_UNSPECIFIED,
|
||||||
StreamFlags::empty(),
|
StreamFlags::empty(),
|
||||||
None,
|
None,
|
||||||
).unwrap(),
|
)
|
||||||
);;
|
.unwrap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.0.as_mut().unwrap().start().unwrap();
|
self.0.as_mut().unwrap().start().unwrap();
|
||||||
|
|
|
@ -14,7 +14,11 @@ pub struct PulseAudioSink {
|
||||||
desc: CString,
|
desc: CString,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T>
|
fn call_pulseaudio<T, F, FailCheck>(
|
||||||
|
f: F,
|
||||||
|
fail_check: FailCheck,
|
||||||
|
kind: io::ErrorKind,
|
||||||
|
) -> io::Result<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
F: Fn(*mut libc::c_int) -> T,
|
F: Fn(*mut libc::c_int) -> T,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
extern crate rodio;
|
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
use std::{io, thread, time};
|
extern crate rodio;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use std::{io, thread, time};
|
||||||
|
|
||||||
pub struct RodioSink {
|
pub struct RodioSink {
|
||||||
rodio_sink: rodio::Sink,
|
rodio_sink: rodio::Sink,
|
||||||
|
@ -14,7 +14,7 @@ fn list_formats(ref device: &rodio::Device) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Error getting default rodio::Sink format: {:?}", e);
|
warn!("Error getting default rodio::Sink format: {:?}", e);
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output_formats = match device.supported_output_formats() {
|
let mut output_formats = match device.supported_output_formats() {
|
||||||
|
@ -22,13 +22,16 @@ fn list_formats(ref device: &rodio::Device) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Error getting supported rodio::Sink formats: {:?}", e);
|
warn!("Error getting supported rodio::Sink formats: {:?}", e);
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if output_formats.peek().is_some() {
|
if output_formats.peek().is_some() {
|
||||||
debug!(" Available formats:");
|
debug!(" Available formats:");
|
||||||
for format in output_formats {
|
for format in output_formats {
|
||||||
let s = format!("{}ch, {:?}, min {:?}, max {:?}", format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate);
|
let s = format!(
|
||||||
|
"{}ch, {:?}, min {:?}, max {:?}",
|
||||||
|
format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate
|
||||||
|
);
|
||||||
if format == default_fmt {
|
if format == default_fmt {
|
||||||
debug!(" (default) {}", s);
|
debug!(" (default) {}", s);
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,9 +82,7 @@ impl Open for RodioSink {
|
||||||
}
|
}
|
||||||
let sink = rodio::Sink::new(&rodio_device);
|
let sink = rodio::Sink::new(&rodio_device);
|
||||||
|
|
||||||
RodioSink {
|
RodioSink { rodio_sink: sink }
|
||||||
rodio_sink: sink,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,17 @@ pub struct AlsaMixer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlsaMixer {
|
impl AlsaMixer {
|
||||||
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
|
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<dyn Error>> {
|
||||||
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
|
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
|
||||||
let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
|
let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
|
||||||
|
|
||||||
let selem = mixer
|
let selem = mixer.find_selem(&sid).expect(
|
||||||
.find_selem(&sid)
|
format!(
|
||||||
.expect(format!("Couldn't find simple mixer control for {}", self.config.mixer).as_str());
|
"Couldn't find simple mixer control for {}",
|
||||||
|
self.config.mixer
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
let (min, max) = selem.get_playback_volume_range();
|
let (min, max) = selem.get_playback_volume_range();
|
||||||
let range = (max - min) as f64;
|
let range = (max - min) as f64;
|
||||||
|
|
||||||
|
@ -72,7 +76,7 @@ impl Mixer for AlsaMixer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
pub trait Mixer: Send {
|
pub trait Mixer: Send {
|
||||||
fn open(Option<MixerConfig>) -> Self
|
fn open(_: Option<MixerConfig>) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn start(&self);
|
fn start(&self);
|
||||||
fn stop(&self);
|
fn stop(&self);
|
||||||
fn set_volume(&self, volume: u16);
|
fn set_volume(&self, volume: u16);
|
||||||
fn volume(&self) -> u16;
|
fn volume(&self) -> u16;
|
||||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,11 @@ pub struct MixerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MixerConfig {
|
impl Default for MixerConfig {
|
||||||
fn default() -> MixerConfig { MixerConfig {
|
fn default() -> MixerConfig {
|
||||||
card: String::from("default"),
|
MixerConfig {
|
||||||
mixer: String::from("PCM"),
|
card: String::from("default"),
|
||||||
index: 0,
|
mixer: String::from("PCM"),
|
||||||
|
index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,11 +40,11 @@ impl Default for MixerConfig {
|
||||||
pub mod softmixer;
|
pub mod softmixer;
|
||||||
use self::softmixer::SoftMixer;
|
use self::softmixer::SoftMixer;
|
||||||
|
|
||||||
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> {
|
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<dyn Mixer> {
|
||||||
Box::new(M::open(device))
|
Box::new(M::open(device))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> {
|
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<dyn Mixer>> {
|
||||||
match name.as_ref().map(AsRef::as_ref) {
|
match name.as_ref().map(AsRef::as_ref) {
|
||||||
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
||||||
#[cfg(feature = "alsa-backend")]
|
#[cfg(feature = "alsa-backend")]
|
||||||
|
|
|
@ -23,7 +23,7 @@ impl Mixer for SoftMixer {
|
||||||
fn set_volume(&self, volume: u16) {
|
fn set_volume(&self, volume: u16) {
|
||||||
self.volume.store(volume as usize, Ordering::Relaxed);
|
self.volume.store(volume as usize, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||||
Some(Box::new(SoftVolumeApplier {
|
Some(Box::new(SoftVolumeApplier {
|
||||||
volume: self.volume.clone(),
|
volume: self.volume.clone(),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use config::{Bitrate, PlayerConfig};
|
use crate::config::{Bitrate, PlayerConfig};
|
||||||
use librespot_core::session::Session;
|
use librespot_core::session::Session;
|
||||||
use librespot_core::spotify_id::SpotifyId;
|
use librespot_core::spotify_id::SpotifyId;
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ struct PlayerInternal {
|
||||||
commands: std::sync::mpsc::Receiver<PlayerCommand>,
|
commands: std::sync::mpsc::Receiver<PlayerCommand>,
|
||||||
|
|
||||||
state: PlayerState,
|
state: PlayerState,
|
||||||
sink: Box<Sink>,
|
sink: Box<dyn Sink>,
|
||||||
sink_running: bool,
|
sink_running: bool,
|
||||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
||||||
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,
|
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +98,10 @@ impl NormalisationData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
|
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
|
||||||
let mut normalisation_factor =
|
let mut normalisation_factor = f32::powf(
|
||||||
f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0);
|
10.0,
|
||||||
|
(data.track_gain_db + config.normalisation_pregain) / 20.0,
|
||||||
|
);
|
||||||
|
|
||||||
if normalisation_factor * data.track_peak > 1.0 {
|
if normalisation_factor * data.track_peak > 1.0 {
|
||||||
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
||||||
|
@ -117,11 +119,11 @@ impl Player {
|
||||||
pub fn new<F>(
|
pub fn new<F>(
|
||||||
config: PlayerConfig,
|
config: PlayerConfig,
|
||||||
session: Session,
|
session: Session,
|
||||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
||||||
sink_builder: F,
|
sink_builder: F,
|
||||||
) -> (Player, PlayerEventChannel)
|
) -> (Player, PlayerEventChannel)
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Box<Sink> + Send + 'static,
|
F: FnOnce() -> Box<dyn Sink> + Send + 'static,
|
||||||
{
|
{
|
||||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
||||||
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
|
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
|
||||||
|
@ -238,7 +240,12 @@ impl PlayerState {
|
||||||
use self::PlayerState::*;
|
use self::PlayerState::*;
|
||||||
match *self {
|
match *self {
|
||||||
Stopped | EndOfTrack { .. } => None,
|
Stopped | EndOfTrack { .. } => None,
|
||||||
Paused { ref mut decoder, .. } | Playing { ref mut decoder, .. } => Some(decoder),
|
Paused {
|
||||||
|
ref mut decoder, ..
|
||||||
|
}
|
||||||
|
| Playing {
|
||||||
|
ref mut decoder, ..
|
||||||
|
} => Some(decoder),
|
||||||
Invalid => panic!("invalid state"),
|
Invalid => panic!("invalid state"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -689,7 +696,9 @@ impl PlayerInternal {
|
||||||
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
||||||
|
|
||||||
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
||||||
Ok(normalisation_data) => NormalisationData::get_factor(&self.config, normalisation_data),
|
Ok(normalisation_data) => {
|
||||||
|
NormalisationData::get_factor(&self.config, normalisation_data)
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Unable to extract normalisation data, using default value.");
|
warn!("Unable to extract normalisation data, using default value.");
|
||||||
1.0 as f32
|
1.0 as f32
|
||||||
|
@ -768,7 +777,7 @@ impl<T: Read + Seek> Seek for Subfile<T> {
|
||||||
x => x,
|
x => x,
|
||||||
};
|
};
|
||||||
|
|
||||||
let newpos = try!(self.stream.seek(pos));
|
let newpos = self.stream.seek(pos)?;
|
||||||
if newpos > self.offset {
|
if newpos > self.offset {
|
||||||
Ok(newpos - self.offset)
|
Ok(newpos - self.offset)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
extern crate protobuf_codegen; // Does the business
|
extern crate protobuf_codegen; // Does the business
|
||||||
extern crate protobuf_codegen_pure; // Helper function
|
extern crate protobuf_codegen_pure; // Helper function
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use std::fs::{read_to_string, write};
|
use std::fs::{read_to_string, write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use protobuf_codegen_pure::Customize;
|
|
||||||
use protobuf_codegen_pure::parse_and_typecheck;
|
use protobuf_codegen_pure::parse_and_typecheck;
|
||||||
|
use protobuf_codegen_pure::Customize;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let customizations = Customize { ..Default::default() };
|
let customizations = Customize {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let lib_str = read_to_string("src/lib.rs").unwrap();
|
let lib_str = read_to_string("src/lib.rs").unwrap();
|
||||||
|
|
||||||
|
@ -44,11 +46,7 @@ fn main() {
|
||||||
let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc");
|
let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc");
|
||||||
// But generate them with the protobuf-codegen crate directly.
|
// But generate them with the protobuf-codegen crate directly.
|
||||||
// Then we can keep the result in-memory.
|
// Then we can keep the result in-memory.
|
||||||
let result = protobuf_codegen::gen(
|
let result = protobuf_codegen::gen(&p.file_descriptors, &p.relative_paths, &customizations);
|
||||||
&p.file_descriptors,
|
|
||||||
&p.relative_paths,
|
|
||||||
&customizations,
|
|
||||||
);
|
|
||||||
// Protoc result as a byte array.
|
// Protoc result as a byte array.
|
||||||
let new = &result.first().unwrap().content;
|
let new = &result.first().unwrap().content;
|
||||||
// Convert to utf8 to compare with existing.
|
// Convert to utf8 to compare with existing.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
max_width = 105
|
# max_width = 105
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
reorder_imports_in_group = true
|
|
||||||
reorder_modules = true
|
reorder_modules = true
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -184,7 +184,13 @@ fn setup(args: &[String]) -> Setup {
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
|
writeln!(
|
||||||
|
stderr(),
|
||||||
|
"error: {}\n{}",
|
||||||
|
f.to_string(),
|
||||||
|
usage(&args[0], &opts)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -218,7 +224,9 @@ fn setup(args: &[String]) -> Setup {
|
||||||
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
|
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
|
||||||
|
|
||||||
let mixer_config = MixerConfig {
|
let mixer_config = MixerConfig {
|
||||||
card: matches.opt_str("mixer-card").unwrap_or(String::from("default")),
|
card: matches
|
||||||
|
.opt_str("mixer-card")
|
||||||
|
.unwrap_or(String::from("default")),
|
||||||
mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
|
mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
|
||||||
index: matches
|
index: matches
|
||||||
.opt_str("mixer-index")
|
.opt_str("mixer-index")
|
||||||
|
@ -400,7 +408,8 @@ impl Main {
|
||||||
let config = task.connect_config.clone();
|
let config = task.connect_config.clone();
|
||||||
let device_id = task.session_config.device_id.clone();
|
let device_id = task.session_config.device_id.clone();
|
||||||
|
|
||||||
task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
|
task.discovery =
|
||||||
|
Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(credentials) = setup.credentials {
|
if let Some(credentials) = setup.credentials {
|
||||||
|
@ -433,7 +442,9 @@ impl Future for Main {
|
||||||
loop {
|
loop {
|
||||||
let mut progress = false;
|
let mut progress = false;
|
||||||
|
|
||||||
if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
|
if let Some(Async::Ready(Some(creds))) =
|
||||||
|
self.discovery.as_mut().map(|d| d.poll().unwrap())
|
||||||
|
{
|
||||||
if let Some(ref spirc) = self.spirc {
|
if let Some(ref spirc) = self.spirc {
|
||||||
spirc.shutdown();
|
spirc.shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use log::info;
|
use log::info;
|
||||||
use librespot::playback::player::PlayerEvent;
|
use librespot::playback::player::PlayerEvent;
|
||||||
use tokio_process::{Child, CommandExt};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use tokio_process::{Child, CommandExt};
|
||||||
|
|
||||||
fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Child> {
|
fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Child> {
|
||||||
let mut v: Vec<&str> = program.split_whitespace().collect();
|
let mut v: Vec<&str> = program.split_whitespace().collect();
|
||||||
|
|
Loading…
Reference in a new issue