mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
rustfmt
This commit is contained in:
parent
5464647164
commit
90eeed3f80
22 changed files with 380 additions and 342 deletions
|
@ -6,9 +6,8 @@ use std::ops::Add;
|
||||||
|
|
||||||
use audio_key::AudioKey;
|
use audio_key::AudioKey;
|
||||||
|
|
||||||
const AUDIO_AESIV : &'static [u8] = &[
|
const AUDIO_AESIV: &'static [u8] = &[0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8,
|
||||||
0x72,0xe0,0x67,0xfb,0xdd,0xcb,0xcf,0x77,0xeb,0xe8,0xbc,0x64,0x3f,0x63,0x0d,0x93,
|
0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93];
|
||||||
];
|
|
||||||
|
|
||||||
pub struct AudioDecrypt<T: io::Read> {
|
pub struct AudioDecrypt<T: io::Read> {
|
||||||
cipher: Box<SynchronousStreamCipher + 'static>,
|
cipher: Box<SynchronousStreamCipher + 'static>,
|
||||||
|
@ -18,9 +17,7 @@ pub struct AudioDecrypt<T : io::Read> {
|
||||||
|
|
||||||
impl<T: io::Read> AudioDecrypt<T> {
|
impl<T: io::Read> AudioDecrypt<T> {
|
||||||
pub fn new(key: AudioKey, reader: T) -> AudioDecrypt<T> {
|
pub fn new(key: AudioKey, reader: T) -> AudioDecrypt<T> {
|
||||||
let cipher = aes::ctr(aes::KeySize::KeySize128,
|
let cipher = aes::ctr(aes::KeySize::KeySize128, &key, AUDIO_AESIV);
|
||||||
&key,
|
|
||||||
AUDIO_AESIV);
|
|
||||||
AudioDecrypt {
|
AudioDecrypt {
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -48,9 +45,7 @@ impl <T : io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
|
||||||
let iv = BigUint::from_bytes_be(AUDIO_AESIV)
|
let iv = BigUint::from_bytes_be(AUDIO_AESIV)
|
||||||
.add(BigUint::from_u64(newpos / 16).unwrap())
|
.add(BigUint::from_u64(newpos / 16).unwrap())
|
||||||
.to_bytes_be();
|
.to_bytes_be();
|
||||||
self.cipher = aes::ctr(aes::KeySize::KeySize128,
|
self.cipher = aes::ctr(aes::KeySize::KeySize128, &self.key, &iv);
|
||||||
&self.key,
|
|
||||||
&iv);
|
|
||||||
|
|
||||||
let buf = vec![0u8; skip as usize];
|
let buf = vec![0u8; skip as usize];
|
||||||
let mut buf2 = vec![0u8; skip as usize];
|
let mut buf2 = vec![0u8; skip as usize];
|
||||||
|
@ -59,5 +54,3 @@ impl <T : io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
|
||||||
Ok(newpos as u64)
|
Ok(newpos as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const CHUNK_SIZE : usize = 0x20000;
|
||||||
|
|
||||||
pub enum AudioFile {
|
pub enum AudioFile {
|
||||||
Direct(fs::File),
|
Direct(fs::File),
|
||||||
Loading(AudioFileLoading)
|
Loading(AudioFileLoading),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioFileLoading {
|
pub struct AudioFileLoading {
|
||||||
|
@ -43,15 +43,18 @@ impl AudioFileLoading {
|
||||||
let read_file = files_iter.next().unwrap();
|
let read_file = files_iter.next().unwrap();
|
||||||
let mut write_file = files_iter.next().unwrap();
|
let mut write_file = files_iter.next().unwrap();
|
||||||
|
|
||||||
let size = session.stream(file_id, 0, 1).into_iter()
|
let size = session.stream(file_id, 0, 1)
|
||||||
|
.into_iter()
|
||||||
.filter_map(|event| {
|
.filter_map(|event| {
|
||||||
match event {
|
match event {
|
||||||
StreamEvent::Header(id, ref data) if id == 0x3 => {
|
StreamEvent::Header(id, ref data) if id == 0x3 => {
|
||||||
Some(BigEndian::read_u32(data) as usize * 4)
|
Some(BigEndian::read_u32(data) as usize * 4)
|
||||||
}
|
}
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}).next().unwrap();
|
})
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let chunk_count = (size + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
let chunk_count = (size + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
|
|
||||||
|
@ -70,9 +73,7 @@ impl AudioFileLoading {
|
||||||
let _shared = shared.clone();
|
let _shared = shared.clone();
|
||||||
let _session = session.clone();
|
let _session = session.clone();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || AudioFileLoading::fetch(&_session, _shared, write_file, seek_rx));
|
||||||
AudioFileLoading::fetch(&_session, _shared, write_file, seek_rx)
|
|
||||||
});
|
|
||||||
|
|
||||||
AudioFileLoading {
|
AudioFileLoading {
|
||||||
read_file: read_file,
|
read_file: read_file,
|
||||||
|
@ -80,12 +81,14 @@ impl AudioFileLoading {
|
||||||
position: 0,
|
position: 0,
|
||||||
seek: seek_tx,
|
seek: seek_tx,
|
||||||
|
|
||||||
shared: shared
|
shared: shared,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(session: &Session, shared: Arc<AudioFileShared>,
|
fn fetch(session: &Session,
|
||||||
mut write_file: TempFile, seek_rx: mpsc::Receiver<u64>) {
|
shared: Arc<AudioFileShared>,
|
||||||
|
mut write_file: TempFile,
|
||||||
|
seek_rx: mpsc::Receiver<u64>) {
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -113,8 +116,10 @@ impl AudioFileLoading {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_chunk(session: &Session, shared: &Arc<AudioFileShared>,
|
fn fetch_chunk(session: &Session,
|
||||||
write_file: &mut TempFile, index: usize) {
|
shared: &Arc<AudioFileShared>,
|
||||||
|
write_file: &mut TempFile,
|
||||||
|
index: usize) {
|
||||||
|
|
||||||
let rx = session.stream(shared.file_id,
|
let rx = session.stream(shared.file_id,
|
||||||
(index * CHUNK_SIZE / 4) as u32,
|
(index * CHUNK_SIZE / 4) as u32,
|
||||||
|
@ -133,7 +138,7 @@ impl AudioFileLoading {
|
||||||
|
|
||||||
size += data.len();
|
size += data.len();
|
||||||
if size >= CHUNK_SIZE {
|
if size >= CHUNK_SIZE {
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +155,8 @@ impl AudioFileLoading {
|
||||||
|
|
||||||
mkdir_existing(&AudioFileManager::cache_dir(session, shared.file_id)).unwrap();
|
mkdir_existing(&AudioFileManager::cache_dir(session, shared.file_id)).unwrap();
|
||||||
|
|
||||||
let mut f = fs::File::create(AudioFileManager::cache_path(session, shared.file_id)).unwrap();
|
let mut f = fs::File::create(AudioFileManager::cache_path(session, shared.file_id))
|
||||||
|
.unwrap();
|
||||||
io::copy(write_file, &mut f).unwrap();
|
io::copy(write_file, &mut f).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,11 +185,9 @@ impl Seek for AudioFileLoading {
|
||||||
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 = try!(self.read_file.seek(pos));
|
||||||
|
|
||||||
/*
|
// Notify the fetch thread to get the correct block
|
||||||
* Notify the fetch thread to get the correct block
|
// This can fail if fetch thread has completed, in which case the
|
||||||
* This can fail if fetch thread has completed, in which case the
|
// block is ready. Just ignore the error.
|
||||||
* block is ready. Just ignore the error.
|
|
||||||
*/
|
|
||||||
self.seek.send(self.position).ignore();
|
self.seek.send(self.position).ignore();
|
||||||
Ok(self.position as u64)
|
Ok(self.position as u64)
|
||||||
}
|
}
|
||||||
|
@ -226,8 +230,7 @@ impl AudioFileManager {
|
||||||
pub fn request(&mut self, session: &Session, file_id: FileId) -> AudioFile {
|
pub fn request(&mut self, session: &Session, file_id: FileId) -> AudioFile {
|
||||||
match fs::File::open(AudioFileManager::cache_path(session, file_id)) {
|
match fs::File::open(AudioFileManager::cache_path(session, file_id)) {
|
||||||
Ok(f) => AudioFile::Direct(f),
|
Ok(f) => AudioFile::Direct(f),
|
||||||
Err(..) => AudioFile::Loading(AudioFileLoading::new(session, file_id))
|
Err(..) => AudioFile::Loading(AudioFileLoading::new(session, file_id)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct AudioKeyId(SpotifyId, FileId);
|
||||||
enum AudioKeyStatus {
|
enum AudioKeyStatus {
|
||||||
Loading(Vec<eventual::Complete<AudioKey, AudioKeyError>>),
|
Loading(Vec<eventual::Complete<AudioKey, AudioKeyError>>),
|
||||||
Loaded(AudioKey),
|
Loaded(AudioKey),
|
||||||
Failed(AudioKeyError)
|
Failed(AudioKeyError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioKeyManager {
|
pub struct AudioKeyManager {
|
||||||
|
@ -32,27 +32,31 @@ impl AudioKeyManager {
|
||||||
AudioKeyManager {
|
AudioKeyManager {
|
||||||
next_seq: 1,
|
next_seq: 1,
|
||||||
pending: HashMap::new(),
|
pending: HashMap::new(),
|
||||||
cache: HashMap::new()
|
cache: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(&mut self, session: &Session, track: SpotifyId, file: FileId)
|
pub fn request(&mut self,
|
||||||
|
session: &Session,
|
||||||
|
track: SpotifyId,
|
||||||
|
file: FileId)
|
||||||
-> eventual::Future<AudioKey, AudioKeyError> {
|
-> eventual::Future<AudioKey, AudioKeyError> {
|
||||||
|
|
||||||
let id = AudioKeyId(track, file);
|
let id = AudioKeyId(track, file);
|
||||||
self.cache.get_mut(&id).map(|status| match *status {
|
self.cache
|
||||||
AudioKeyStatus::Failed(error) => {
|
.get_mut(&id)
|
||||||
eventual::Future::error(error)
|
.map(|status| {
|
||||||
}
|
match *status {
|
||||||
AudioKeyStatus::Loaded(key) => {
|
AudioKeyStatus::Failed(error) => eventual::Future::error(error),
|
||||||
eventual::Future::of(key)
|
AudioKeyStatus::Loaded(key) => eventual::Future::of(key),
|
||||||
}
|
|
||||||
AudioKeyStatus::Loading(ref mut req) => {
|
AudioKeyStatus::Loading(ref mut req) => {
|
||||||
let (tx, rx) = eventual::Future::pair();
|
let (tx, rx) = eventual::Future::pair();
|
||||||
req.push(tx);
|
req.push(tx);
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
}).unwrap_or_else(|| {
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
let seq = self.next_seq;
|
let seq = self.next_seq;
|
||||||
self.next_seq += 1;
|
self.next_seq += 1;
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ impl AudioKeyManager {
|
||||||
self.pending.insert(seq, id.clone());
|
self.pending.insert(seq, id.clone());
|
||||||
|
|
||||||
let (tx, rx) = eventual::Future::pair();
|
let (tx, rx) = eventual::Future::pair();
|
||||||
self.cache.insert(id, AudioKeyStatus::Loading(vec!{ tx }));
|
self.cache.insert(id, AudioKeyStatus::Loading(vec![tx]));
|
||||||
rx
|
rx
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -78,7 +82,7 @@ impl PacketHandler for AudioKeyManager {
|
||||||
let mut data = Cursor::new(data);
|
let mut data = Cursor::new(data);
|
||||||
let seq = data.read_u32::<BigEndian>().unwrap();
|
let seq = data.read_u32::<BigEndian>().unwrap();
|
||||||
|
|
||||||
if let Some(status) = self.pending.remove(&seq).and_then(|id| { self.cache.get_mut(&id) }) {
|
if let Some(status) = self.pending.remove(&seq).and_then(|id| self.cache.get_mut(&id)) {
|
||||||
if cmd == 0xd {
|
if cmd == 0xd {
|
||||||
let mut key = [0u8; 16];
|
let mut key = [0u8; 16];
|
||||||
data.read_exact(&mut key).unwrap();
|
data.read_exact(&mut key).unwrap();
|
||||||
|
@ -103,4 +107,3 @@ impl PacketHandler for AudioKeyManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
|
||||||
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 = try!(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 = try!(read_u8(stream)) as u32;
|
||||||
|
@ -40,7 +40,11 @@ fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
fn login(&self, username: String, auth_data: Vec<u8>, typ: AuthenticationType) -> Result<(), ()> {
|
fn login(&self,
|
||||||
|
username: String,
|
||||||
|
auth_data: Vec<u8>,
|
||||||
|
typ: AuthenticationType)
|
||||||
|
-> Result<(), ()> {
|
||||||
let packet = protobuf_init!(protocol::authentication::ClientResponseEncrypted::new(), {
|
let packet = protobuf_init!(protocol::authentication::ClientResponseEncrypted::new(), {
|
||||||
login_credentials => {
|
login_credentials => {
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -78,8 +82,8 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
0xad => {
|
0xad => {
|
||||||
let msg : protocol::keyexchange::APLoginFailed =
|
let msg: protocol::keyexchange::APLoginFailed = protobuf::parse_from_bytes(&data)
|
||||||
protobuf::parse_from_bytes(&data).unwrap();
|
.unwrap();
|
||||||
eprintln!("Authentication failed, {:?}", msg);
|
eprintln!("Authentication failed, {:?}", msg);
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
@ -91,7 +95,8 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login_password(&self, username: String, password: String) -> Result<(), ()> {
|
pub fn login_password(&self, username: String, password: String) -> Result<(), ()> {
|
||||||
self.login(username, password.into_bytes(),
|
self.login(username,
|
||||||
|
password.into_bytes(),
|
||||||
AuthenticationType::AUTHENTICATION_USER_PASS)
|
AuthenticationType::AUTHENTICATION_USER_PASS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,11 +126,13 @@ impl Session {
|
||||||
let blob = {
|
let blob = {
|
||||||
// Anyone know what this block mode is ?
|
// Anyone know what this block mode is ?
|
||||||
let mut data = vec![0u8; blob.len()];
|
let mut data = vec![0u8; blob.len()];
|
||||||
let mut cipher = aes::ecb_decryptor(
|
let mut cipher = aes::ecb_decryptor(aes::KeySize::KeySize192,
|
||||||
aes::KeySize::KeySize192, &key, crypto::blockmodes::NoPadding);
|
&key,
|
||||||
|
crypto::blockmodes::NoPadding);
|
||||||
cipher.decrypt(&mut crypto::buffer::RefReadBuffer::new(&blob),
|
cipher.decrypt(&mut crypto::buffer::RefReadBuffer::new(&blob),
|
||||||
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
||||||
true).unwrap();
|
true)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let l = blob.len();
|
let l = blob.len();
|
||||||
for i in 0..l - 0x10 {
|
for i in 0..l - 0x10 {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::result;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
IoError(io::Error),
|
IoError(io::Error),
|
||||||
Other
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
@ -24,13 +24,13 @@ impl convert::From<byteorder::Error> for Error {
|
||||||
fn from(err: byteorder::Error) -> Error {
|
fn from(err: byteorder::Error) -> Error {
|
||||||
match err {
|
match err {
|
||||||
byteorder::Error::Io(e) => Error::IoError(e),
|
byteorder::Error::Io(e) => Error::IoError(e),
|
||||||
_ => Error::Other
|
_ => Error::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PlainConnection {
|
pub struct PlainConnection {
|
||||||
stream: TcpStream
|
stream: TcpStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -79,13 +79,12 @@ impl PlainConnection {
|
||||||
|
|
||||||
impl CipherConnection {
|
impl CipherConnection {
|
||||||
pub fn new(stream: TcpStream, recv_key: &[u8], send_key: &[u8]) -> CipherConnection {
|
pub fn new(stream: TcpStream, recv_key: &[u8], send_key: &[u8]) -> CipherConnection {
|
||||||
CipherConnection {
|
CipherConnection { stream: ShannonStream::new(stream, recv_key, send_key) }
|
||||||
stream: ShannonStream::new(stream, recv_key, send_key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_packet(&mut self, cmd: u8, data: &[u8]) -> Result<()> {
|
pub fn send_packet(&mut self, cmd: u8, data: &[u8]) -> Result<()> {
|
||||||
try!(self.stream.write_u8(cmd)); try!(self.stream.write_u16::<BigEndian>(data.len() as u16));
|
try!(self.stream.write_u8(cmd));
|
||||||
|
try!(self.stream.write_u16::<BigEndian>(data.len() as u16));
|
||||||
try!(self.stream.write(data));
|
try!(self.stream.write(data));
|
||||||
|
|
||||||
try!(self.stream.finish_send());
|
try!(self.stream.finish_send());
|
||||||
|
@ -110,4 +109,3 @@ impl CipherConnection {
|
||||||
pub trait PacketHandler {
|
pub trait PacketHandler {
|
||||||
fn handle(&mut self, cmd: u8, data: Vec<u8>);
|
fn handle(&mut self, cmd: u8, data: Vec<u8>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,4 +48,3 @@ impl DHLocalKeys {
|
||||||
shared_key.to_bytes_be()
|
shared_key.to_bytes_be()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use util;
|
||||||
pub struct DiscoveryManager {
|
pub struct DiscoveryManager {
|
||||||
session: Session,
|
session: Session,
|
||||||
private_key: BigUint,
|
private_key: BigUint,
|
||||||
public_key: BigUint
|
public_key: BigUint,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found() -> ResponseBox {
|
fn not_found() -> ResponseBox {
|
||||||
|
@ -100,7 +100,9 @@ impl DiscoveryManager {
|
||||||
|
|
||||||
let decrypted = {
|
let decrypted = {
|
||||||
let mut data = vec![0u8; encrypted.len()];
|
let mut data = vec![0u8; encrypted.len()];
|
||||||
let mut cipher = crypto::aes::ctr(crypto::aes::KeySize::KeySize128, &encryption_key[0..16], &iv);
|
let mut cipher = crypto::aes::ctr(crypto::aes::KeySize::KeySize128,
|
||||||
|
&encryption_key[0..16],
|
||||||
|
&iv);
|
||||||
cipher.process(&encrypted, &mut data);
|
cipher.process(&encrypted, &mut data);
|
||||||
String::from_utf8(data).unwrap()
|
String::from_utf8(data).unwrap()
|
||||||
};
|
};
|
||||||
|
@ -126,7 +128,8 @@ impl DiscoveryManager {
|
||||||
|
|
||||||
for mut request in server.incoming_requests() {
|
for mut request in server.incoming_requests() {
|
||||||
let (_, query, _) = url::parse_path(request.url()).unwrap();
|
let (_, query, _) = url::parse_path(request.url()).unwrap();
|
||||||
let mut params = query.map(|q| url::form_urlencoded::parse(q.as_bytes())).unwrap_or(Vec::new());
|
let mut params = query.map(|q| url::form_urlencoded::parse(q.as_bytes()))
|
||||||
|
.unwrap_or(Vec::new());
|
||||||
|
|
||||||
if *request.method() == Method::Post {
|
if *request.method() == Method::Post {
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
|
@ -151,4 +154,3 @@ impl DiscoveryManager {
|
||||||
drop(svc);
|
drop(svc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
#![plugin(protobuf_macros)]
|
#![plugin(protobuf_macros)]
|
||||||
#![plugin(json_macros)]
|
#![plugin(json_macros)]
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
extern crate bit_set;
|
extern crate bit_set;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
@ -42,4 +43,3 @@ pub mod session;
|
||||||
pub mod spirc;
|
pub mod spirc;
|
||||||
mod stream;
|
mod stream;
|
||||||
mod zeroconf;
|
mod zeroconf;
|
||||||
|
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -36,7 +36,7 @@ fn main() {
|
||||||
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE");
|
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE");
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => { m },
|
Ok(m) => m,
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
print!("Error: {}\n{}", f.to_string(), usage(&*program, &opts));
|
print!("Error: {}\n{}", f.to_string(), usage(&*program, &opts));
|
||||||
return;
|
return;
|
||||||
|
@ -44,9 +44,8 @@ fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let appkey = {
|
let appkey = {
|
||||||
let mut file = File::open(
|
let mut file = File::open(Path::new(&*matches.opt_str("a").unwrap()))
|
||||||
Path::new(&*matches.opt_str("a").unwrap())
|
.expect("Could not open app key.");
|
||||||
).expect("Could not open app key.");
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
file.read_to_end(&mut data).unwrap();
|
file.read_to_end(&mut data).unwrap();
|
||||||
|
@ -59,9 +58,9 @@ fn main() {
|
||||||
let name = matches.opt_str("n").unwrap();
|
let name = matches.opt_str("n").unwrap();
|
||||||
|
|
||||||
let credentials = username.map(|u| {
|
let credentials = username.map(|u| {
|
||||||
let password = matches.opt_str("p").or_else(|| {
|
let password = matches.opt_str("p")
|
||||||
std::env::var(PASSWORD_ENV_NAME).ok()
|
.or_else(|| std::env::var(PASSWORD_ENV_NAME).ok())
|
||||||
}).unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
print!("Password: ");
|
print!("Password: ");
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
read_password().unwrap()
|
read_password().unwrap()
|
||||||
|
@ -85,7 +84,7 @@ fn main() {
|
||||||
user_agent: version_string(),
|
user_agent: version_string(),
|
||||||
device_name: name,
|
device_name: name,
|
||||||
cache_location: PathBuf::from(cache_location),
|
cache_location: PathBuf::from(cache_location),
|
||||||
bitrate: bitrate
|
bitrate: bitrate,
|
||||||
};
|
};
|
||||||
|
|
||||||
let session = Session::new(config);
|
let session = Session::new(config);
|
||||||
|
@ -99,12 +98,9 @@ fn main() {
|
||||||
|
|
||||||
let player = Player::new(session.clone());
|
let player = Player::new(session.clone());
|
||||||
let mut spirc = SpircManager::new(session.clone(), player);
|
let mut spirc = SpircManager::new(session.clone(), player);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || spirc.run());
|
||||||
spirc.run()
|
|
||||||
});
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
session.poll();
|
session.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,13 @@ pub struct MercuryRequest {
|
||||||
pub method: MercuryMethod,
|
pub method: MercuryMethod,
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub payload: Vec<Vec<u8>>
|
pub payload: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MercuryResponse {
|
pub struct MercuryResponse {
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
pub payload: Vec<Vec<u8>>
|
pub payload: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MercuryCallback {
|
enum MercuryCallback {
|
||||||
|
@ -40,7 +40,7 @@ enum MercuryCallback {
|
||||||
pub struct MercuryPending {
|
pub struct MercuryPending {
|
||||||
parts: Vec<Vec<u8>>,
|
parts: Vec<Vec<u8>>,
|
||||||
partial: Option<Vec<u8>>,
|
partial: Option<Vec<u8>>,
|
||||||
callback: MercuryCallback
|
callback: MercuryCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MercuryManager {
|
pub struct MercuryManager {
|
||||||
|
@ -55,8 +55,9 @@ impl ToString for MercuryMethod {
|
||||||
MercuryMethod::GET => "GET",
|
MercuryMethod::GET => "GET",
|
||||||
MercuryMethod::SUB => "SUB",
|
MercuryMethod::SUB => "SUB",
|
||||||
MercuryMethod::UNSUB => "UNSUB",
|
MercuryMethod::UNSUB => "UNSUB",
|
||||||
MercuryMethod::SEND => "SEND"
|
MercuryMethod::SEND => "SEND",
|
||||||
}.to_owned()
|
}
|
||||||
|
.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,30 +87,34 @@ impl MercuryManager {
|
||||||
|
|
||||||
session.send_packet(cmd, &data).unwrap();
|
session.send_packet(cmd, &data).unwrap();
|
||||||
|
|
||||||
self.pending.insert(seq.to_vec(), MercuryPending{
|
self.pending.insert(seq.to_vec(),
|
||||||
|
MercuryPending {
|
||||||
parts: Vec::new(),
|
parts: Vec::new(),
|
||||||
partial: None,
|
partial: None,
|
||||||
callback: cb,
|
callback: cb,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(&mut self, session: &Session, req: MercuryRequest)
|
pub fn request(&mut self,
|
||||||
|
session: &Session,
|
||||||
|
req: MercuryRequest)
|
||||||
-> eventual::Future<MercuryResponse, ()> {
|
-> eventual::Future<MercuryResponse, ()> {
|
||||||
let (tx, rx) = eventual::Future::pair();
|
let (tx, rx) = eventual::Future::pair();
|
||||||
self.request_with_callback(session, req, MercuryCallback::Future(tx));
|
self.request_with_callback(session, req, MercuryCallback::Future(tx));
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe(&mut self, session: &Session, uri: String)
|
pub fn subscribe(&mut self, session: &Session, uri: String) -> mpsc::Receiver<MercuryResponse> {
|
||||||
-> mpsc::Receiver<MercuryResponse> {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
self.request_with_callback(session, MercuryRequest{
|
self.request_with_callback(session,
|
||||||
|
MercuryRequest {
|
||||||
method: MercuryMethod::SUB,
|
method: MercuryMethod::SUB,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
content_type: None,
|
content_type: None,
|
||||||
payload: Vec::new()
|
payload: Vec::new(),
|
||||||
}, MercuryCallback::Subscription(tx));
|
},
|
||||||
|
MercuryCallback::Subscription(tx));
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
@ -126,7 +131,8 @@ impl MercuryManager {
|
||||||
response: MercuryResponse,
|
response: MercuryResponse,
|
||||||
tx: mpsc::Sender<MercuryResponse>) {
|
tx: mpsc::Sender<MercuryResponse>) {
|
||||||
for sub_data in response.payload {
|
for sub_data in response.payload {
|
||||||
if let Ok(mut sub) = protobuf::parse_from_bytes::<protocol::pubsub::Subscription>(&sub_data) {
|
if let Ok(mut sub) =
|
||||||
|
protobuf::parse_from_bytes::<protocol::pubsub::Subscription>(&sub_data) {
|
||||||
self.subscriptions.insert(sub.take_uri(), tx.clone());
|
self.subscriptions.insert(sub.take_uri(), tx.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,12 +140,11 @@ impl MercuryManager {
|
||||||
|
|
||||||
fn complete_request(&mut self, mut pending: MercuryPending) {
|
fn complete_request(&mut self, mut pending: MercuryPending) {
|
||||||
let header_data = pending.parts.remove(0);
|
let header_data = pending.parts.remove(0);
|
||||||
let header : protocol::mercury::Header =
|
let header: protocol::mercury::Header = protobuf::parse_from_bytes(&header_data).unwrap();
|
||||||
protobuf::parse_from_bytes(&header_data).unwrap();
|
|
||||||
|
|
||||||
let response = MercuryResponse {
|
let response = MercuryResponse {
|
||||||
uri: header.get_uri().to_owned(),
|
uri: header.get_uri().to_owned(),
|
||||||
payload: pending.parts
|
payload: pending.parts,
|
||||||
};
|
};
|
||||||
|
|
||||||
match pending.callback {
|
match pending.callback {
|
||||||
|
@ -203,7 +208,7 @@ impl PacketHandler for MercuryManager {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Ignore seq {:?} cmd {}", seq, cmd);
|
println!("Ignore seq {:?} cmd {}", seq, cmd);
|
||||||
return
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
|
@ -227,4 +232,3 @@ impl PacketHandler for MercuryManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,14 @@ fn countrylist_contains(list: &str, country: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
|
fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
|
||||||
where I : Iterator<Item=&'s protocol::metadata::Restriction> {
|
where I: Iterator<Item = &'s protocol::metadata::Restriction>
|
||||||
restrictions
|
{
|
||||||
.filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()))
|
restrictions.filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()))
|
||||||
.all(|r| !countrylist_contains(r.get_countries_forbidden(), country)
|
.all(|r| {
|
||||||
&& (!r.has_countries_allowed()
|
!countrylist_contains(r.get_countries_forbidden(), country) &&
|
||||||
|| countrylist_contains(r.get_countries_allowed(), country)))
|
(!r.has_countries_allowed() ||
|
||||||
|
countrylist_contains(r.get_countries_allowed(), country))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MetadataTrait : Send + 'static {
|
pub trait MetadataTrait : Send + 'static {
|
||||||
|
@ -43,7 +45,7 @@ pub struct Album {
|
||||||
pub id: SpotifyId,
|
pub id: SpotifyId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub artists: Vec<SpotifyId>,
|
pub artists: Vec<SpotifyId>,
|
||||||
pub covers: Vec<FileId>
|
pub covers: Vec<FileId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -69,18 +71,19 @@ impl MetadataTrait for Track {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()),
|
id: SpotifyId::from_raw(msg.get_gid()),
|
||||||
name: msg.get_name().to_owned(),
|
name: msg.get_name().to_owned(),
|
||||||
album: SpotifyId::from_raw(msg.get_album().get_gid()),
|
album: SpotifyId::from_raw(msg.get_album().get_gid()),
|
||||||
files: msg.get_file().iter()
|
files: msg.get_file()
|
||||||
|
.iter()
|
||||||
.map(|file| {
|
.map(|file| {
|
||||||
let mut dst = [0u8; 20];
|
let mut dst = [0u8; 20];
|
||||||
dst.clone_from_slice(&file.get_file_id());
|
dst.clone_from_slice(&file.get_file_id());
|
||||||
(FileId(dst), file.get_format())
|
(FileId(dst), file.get_format())
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
alternatives: msg.get_alternative().iter()
|
alternatives: msg.get_alternative()
|
||||||
|
.iter()
|
||||||
.map(|alt| SpotifyId::from_raw(alt.get_gid()))
|
.map(|alt| SpotifyId::from_raw(alt.get_gid()))
|
||||||
.collect(),
|
.collect(),
|
||||||
available: parse_restrictions(
|
available: parse_restrictions(msg.get_restriction().iter(),
|
||||||
msg.get_restriction().iter(),
|
|
||||||
&session.0.data.read().unwrap().country,
|
&session.0.data.read().unwrap().country,
|
||||||
"premium"),
|
"premium"),
|
||||||
}
|
}
|
||||||
|
@ -98,10 +101,13 @@ impl MetadataTrait for Album {
|
||||||
Album {
|
Album {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()),
|
id: SpotifyId::from_raw(msg.get_gid()),
|
||||||
name: msg.get_name().to_owned(),
|
name: msg.get_name().to_owned(),
|
||||||
artists: msg.get_artist().iter()
|
artists: msg.get_artist()
|
||||||
|
.iter()
|
||||||
.map(|a| SpotifyId::from_raw(a.get_gid()))
|
.map(|a| SpotifyId::from_raw(a.get_gid()))
|
||||||
.collect(),
|
.collect(),
|
||||||
covers: msg.get_cover_group().get_image().iter()
|
covers: msg.get_cover_group()
|
||||||
|
.get_image()
|
||||||
|
.iter()
|
||||||
.map(|image| {
|
.map(|image| {
|
||||||
let mut dst = [0u8; 20];
|
let mut dst = [0u8; 20];
|
||||||
dst.clone_from_slice(&image.get_file_id());
|
dst.clone_from_slice(&image.get_file_id());
|
||||||
|
@ -135,21 +141,22 @@ impl MetadataManager {
|
||||||
MetadataManager
|
MetadataManager
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<T: MetadataTrait>(&mut self, session: &Session, id: SpotifyId)
|
pub fn get<T: MetadataTrait>(&mut self, session: &Session, id: SpotifyId) -> MetadataRef<T> {
|
||||||
-> MetadataRef<T> {
|
|
||||||
|
|
||||||
let _session = session.clone();
|
let _session = session.clone();
|
||||||
session.mercury(MercuryRequest {
|
session.mercury(MercuryRequest {
|
||||||
method: MercuryMethod::GET,
|
method: MercuryMethod::GET,
|
||||||
uri: format!("{}/{}", T::base_url(), id.to_base16()),
|
uri: format!("{}/{}", T::base_url(), id.to_base16()),
|
||||||
content_type: None,
|
content_type: None,
|
||||||
payload: Vec::new()
|
payload: Vec::new(),
|
||||||
}).and_then(move |response| {
|
})
|
||||||
let msg : T::Message = protobuf::parse_from_bytes(
|
.and_then(move |response| {
|
||||||
response.payload.first().unwrap()).unwrap();
|
let msg: T::Message = protobuf::parse_from_bytes(response.payload
|
||||||
|
.first()
|
||||||
|
.unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
Ok(T::parse(&msg, &_session))
|
Ok(T::parse(&msg, &_session))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct PlayerState {
|
||||||
position_measured_at: i64,
|
position_measured_at: i64,
|
||||||
update_time: i64,
|
update_time: i64,
|
||||||
|
|
||||||
end_of_track: bool
|
end_of_track: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PlayerInternal {
|
struct PlayerInternal {
|
||||||
|
@ -36,7 +36,7 @@ enum PlayerCommand {
|
||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
Stop,
|
Stop,
|
||||||
Seek(u32)
|
Seek(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -49,17 +49,16 @@ impl Player {
|
||||||
position_measured_at: 0,
|
position_measured_at: 0,
|
||||||
update_time: util::now_ms(),
|
update_time: util::now_ms(),
|
||||||
end_of_track: false,
|
end_of_track: false,
|
||||||
}), Condvar::new()));
|
}),
|
||||||
|
Condvar::new()));
|
||||||
|
|
||||||
let internal = PlayerInternal {
|
let internal = PlayerInternal {
|
||||||
session: session,
|
session: session,
|
||||||
commands: cmd_rx,
|
commands: cmd_rx,
|
||||||
state: state.clone()
|
state: state.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || internal.run());
|
||||||
internal.run()
|
|
||||||
});
|
|
||||||
|
|
||||||
Player {
|
Player {
|
||||||
commands: cmd_tx,
|
commands: cmd_tx,
|
||||||
|
@ -113,11 +112,17 @@ impl PlayerInternal {
|
||||||
let mut track = self.session.metadata::<Track>(track_id).await().unwrap();
|
let mut track = self.session.metadata::<Track>(track_id).await().unwrap();
|
||||||
|
|
||||||
if !track.available {
|
if !track.available {
|
||||||
let alternatives = track.alternatives.iter()
|
let alternatives = track.alternatives
|
||||||
.map(|alt_id| self.session.metadata::<Track>(*alt_id))
|
.iter()
|
||||||
|
.map(|alt_id| {
|
||||||
|
self.session.metadata::<Track>(*alt_id)
|
||||||
|
})
|
||||||
.collect::<Vec<TrackRef>>();
|
.collect::<Vec<TrackRef>>();
|
||||||
|
|
||||||
track = eventual::sequence(alternatives.into_iter()).iter().find(|alt| alt.available).unwrap();
|
track = eventual::sequence(alternatives.into_iter())
|
||||||
|
.iter()
|
||||||
|
.find(|alt| alt.available)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let format = match self.session.0.config.bitrate {
|
let format = match self.session.0.config.bitrate {
|
||||||
|
@ -152,11 +157,12 @@ impl PlayerInternal {
|
||||||
Some(PlayerCommand::Seek(ms)) => {
|
Some(PlayerCommand::Seek(ms)) => {
|
||||||
decoder.as_mut().unwrap().time_seek(ms as f64 / 1000f64).unwrap();
|
decoder.as_mut().unwrap().time_seek(ms as f64 / 1000f64).unwrap();
|
||||||
self.update(|state| {
|
self.update(|state| {
|
||||||
state.position_ms = (decoder.as_mut().unwrap().time_tell().unwrap() * 1000f64) as u32;
|
state.position_ms =
|
||||||
|
(decoder.as_mut().unwrap().time_tell().unwrap() * 1000f64) as u32;
|
||||||
state.position_measured_at = util::now_ms();
|
state.position_measured_at = util::now_ms();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
Some(PlayerCommand::Play) => {
|
Some(PlayerCommand::Play) => {
|
||||||
self.update(|state| {
|
self.update(|state| {
|
||||||
state.status = PlayStatus::kPlayStatusPlay;
|
state.status = PlayStatus::kPlayStatusPlay;
|
||||||
|
@ -164,7 +170,7 @@ impl PlayerInternal {
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.start().unwrap();
|
stream.start().unwrap();
|
||||||
},
|
}
|
||||||
Some(PlayerCommand::Pause) => {
|
Some(PlayerCommand::Pause) => {
|
||||||
self.update(|state| {
|
self.update(|state| {
|
||||||
state.status = PlayStatus::kPlayStatusPause;
|
state.status = PlayStatus::kPlayStatusPause;
|
||||||
|
@ -173,7 +179,7 @@ impl PlayerInternal {
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.stop().unwrap();
|
stream.stop().unwrap();
|
||||||
},
|
}
|
||||||
Some(PlayerCommand::Stop) => {
|
Some(PlayerCommand::Stop) => {
|
||||||
self.update(|state| {
|
self.update(|state| {
|
||||||
if state.status == PlayStatus::kPlayStatusPlay {
|
if state.status == PlayStatus::kPlayStatusPlay {
|
||||||
|
@ -184,7 +190,7 @@ impl PlayerInternal {
|
||||||
|
|
||||||
stream.stop().unwrap();
|
stream.stop().unwrap();
|
||||||
decoder = None;
|
decoder = None;
|
||||||
},
|
}
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,11 +199,10 @@ impl PlayerInternal {
|
||||||
Some(Ok(packet)) => {
|
Some(Ok(packet)) => {
|
||||||
match stream.write(&packet.data) {
|
match stream.write(&packet.data) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(portaudio::PaError::OutputUnderflowed)
|
Err(portaudio::PaError::OutputUnderflowed) => eprintln!("Underflow"),
|
||||||
=> eprintln!("Underflow"),
|
Err(e) => panic!("PA Error {}", e),
|
||||||
Err(e) => panic!("PA Error {}", e)
|
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Some(Err(vorbis::VorbisError::Hole)) => (),
|
Some(Err(vorbis::VorbisError::Hole)) => (),
|
||||||
Some(Err(e)) => panic!("Vorbis error {:?}", e),
|
Some(Err(e)) => panic!("Vorbis error {:?}", e),
|
||||||
None => {
|
None => {
|
||||||
|
@ -216,7 +221,8 @@ impl PlayerInternal {
|
||||||
let now = util::now_ms();
|
let now = util::now_ms();
|
||||||
|
|
||||||
if now - state.position_measured_at > 5000 {
|
if now - state.position_measured_at > 5000 {
|
||||||
state.position_ms = (decoder.as_mut().unwrap().time_tell().unwrap() * 1000f64) as u32;
|
state.position_ms =
|
||||||
|
(decoder.as_mut().unwrap().time_tell().unwrap() * 1000f64) as u32;
|
||||||
state.position_measured_at = now;
|
state.position_measured_at = now;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -232,7 +238,8 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update<F>(&self, f: F)
|
fn update<F>(&self, f: F)
|
||||||
where F: FnOnce(&mut MutexGuard<PlayerState>) -> bool {
|
where F: FnOnce(&mut MutexGuard<PlayerState>) -> bool
|
||||||
|
{
|
||||||
let mut guard = self.state.0.lock().unwrap();
|
let mut guard = self.state.0.lock().unwrap();
|
||||||
let update = f(&mut guard);
|
let update = f(&mut guard);
|
||||||
if update {
|
if update {
|
||||||
|
@ -245,8 +252,7 @@ impl PlayerInternal {
|
||||||
impl SpircDelegate for Player {
|
impl SpircDelegate for Player {
|
||||||
type State = PlayerState;
|
type State = PlayerState;
|
||||||
|
|
||||||
fn load(&self, track: SpotifyId,
|
fn load(&self, track: SpotifyId, start_playing: bool, position_ms: u32) {
|
||||||
start_playing: bool, position_ms: u32) {
|
|
||||||
self.command(PlayerCommand::Load(track, start_playing, position_ms));
|
self.command(PlayerCommand::Load(track, start_playing, position_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,4 +314,3 @@ impl SpircState for PlayerState {
|
||||||
return self.end_of_track;
|
return self.end_of_track;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ use util;
|
||||||
pub enum Bitrate {
|
pub enum Bitrate {
|
||||||
Bitrate96,
|
Bitrate96,
|
||||||
Bitrate160,
|
Bitrate160,
|
||||||
Bitrate320
|
Bitrate320,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -123,10 +123,10 @@ impl Session {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let init_client_packet =
|
let init_client_packet = connection.send_packet_prefix(&[0, 4],
|
||||||
connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap()).unwrap();
|
&request.write_to_bytes().unwrap())
|
||||||
let init_server_packet =
|
.unwrap();
|
||||||
connection.recv_packet().unwrap();
|
let init_server_packet = connection.recv_packet().unwrap();
|
||||||
|
|
||||||
let response: protocol::keyexchange::APResponseMessage =
|
let response: protocol::keyexchange::APResponseMessage =
|
||||||
protobuf::parse_from_bytes(&init_server_packet[4..]).unwrap();
|
protobuf::parse_from_bytes(&init_server_packet[4..]).unwrap();
|
||||||
|
@ -169,8 +169,7 @@ impl Session {
|
||||||
|
|
||||||
connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap();
|
connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap();
|
||||||
|
|
||||||
let cipher_connection = CipherConnection::new(
|
let cipher_connection = CipherConnection::new(connection.into_stream(),
|
||||||
connection.into_stream(),
|
|
||||||
&send_key,
|
&send_key,
|
||||||
&recv_key);
|
&recv_key);
|
||||||
|
|
||||||
|
@ -187,11 +186,10 @@ impl Session {
|
||||||
0x9 => self.0.stream.lock().unwrap().handle(cmd, data),
|
0x9 => self.0.stream.lock().unwrap().handle(cmd, data),
|
||||||
0xd | 0xe => self.0.audio_key.lock().unwrap().handle(cmd, data),
|
0xd | 0xe => self.0.audio_key.lock().unwrap().handle(cmd, data),
|
||||||
0x1b => {
|
0x1b => {
|
||||||
self.0.data.write().unwrap().country =
|
self.0.data.write().unwrap().country = String::from_utf8(data).unwrap();
|
||||||
String::from_utf8(data).unwrap();
|
}
|
||||||
},
|
|
||||||
0xb2...0xb6 => self.0.mercury.lock().unwrap().handle(cmd, data),
|
0xb2...0xb6 => self.0.mercury.lock().unwrap().handle(cmd, data),
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,4 +225,3 @@ impl Session {
|
||||||
self.0.mercury.lock().unwrap().subscribe(self, uri)
|
self.0.mercury.lock().unwrap().subscribe(self, uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
src/spirc.rs
33
src/spirc.rs
|
@ -34,14 +34,13 @@ pub struct SpircManager<D: SpircDelegate> {
|
||||||
last_command_msgid: u32,
|
last_command_msgid: u32,
|
||||||
|
|
||||||
tracks: Vec<SpotifyId>,
|
tracks: Vec<SpotifyId>,
|
||||||
index: u32
|
index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SpircDelegate {
|
pub trait SpircDelegate {
|
||||||
type State : SpircState;
|
type State : SpircState;
|
||||||
|
|
||||||
fn load(&self, track: SpotifyId,
|
fn load(&self, track: SpotifyId, start_playing: bool, position_ms: u32);
|
||||||
start_playing: bool, position_ms: u32);
|
|
||||||
fn play(&self);
|
fn play(&self);
|
||||||
fn pause(&self);
|
fn pause(&self);
|
||||||
fn seek(&self, position_ms: u32);
|
fn seek(&self, position_ms: u32);
|
||||||
|
@ -59,8 +58,7 @@ pub trait SpircState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: SpircDelegate> SpircManager<D> {
|
impl<D: SpircDelegate> SpircManager<D> {
|
||||||
pub fn new(session: Session, delegate: D)
|
pub fn new(session: Session, delegate: D) -> SpircManager<D> {
|
||||||
-> SpircManager<D> {
|
|
||||||
|
|
||||||
let ident = session.0.data.read().unwrap().device_id.clone();
|
let ident = session.0.data.read().unwrap().device_id.clone();
|
||||||
let name = session.0.config.device_name.clone();
|
let name = session.0.config.device_name.clone();
|
||||||
|
@ -88,13 +86,19 @@ impl <D: SpircDelegate> SpircManager<D> {
|
||||||
last_command_msgid: 0,
|
last_command_msgid: 0,
|
||||||
|
|
||||||
tracks: Vec::new(),
|
tracks: Vec::new(),
|
||||||
index: 0
|
index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
let rx = self.session.mercury_sub(format!("hm://remote/user/{}/",
|
let rx = self.session.mercury_sub(format!("hm://remote/user/{}/",
|
||||||
self.session.0.data.read().unwrap().canonical_username.clone()));
|
self.session
|
||||||
|
.0
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.canonical_username
|
||||||
|
.clone()));
|
||||||
let updates = self.delegate.updates();
|
let updates = self.delegate.updates();
|
||||||
|
|
||||||
self.notify(true, None);
|
self.notify(true, None);
|
||||||
|
@ -149,7 +153,9 @@ impl <D: SpircDelegate> SpircManager<D> {
|
||||||
|
|
||||||
|
|
||||||
self.index = frame.get_state().get_playing_track_index();
|
self.index = frame.get_state().get_playing_track_index();
|
||||||
self.tracks = frame.get_state().get_track().iter()
|
self.tracks = frame.get_state()
|
||||||
|
.get_track()
|
||||||
|
.iter()
|
||||||
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -173,7 +179,7 @@ impl <D: SpircDelegate> SpircManager<D> {
|
||||||
self.delegate.stop();
|
self.delegate.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,13 +206,16 @@ impl <D: SpircDelegate> SpircManager<D> {
|
||||||
pkt.set_state(self.spirc_state());
|
pkt.set_state(self.spirc_state());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.session.mercury(MercuryRequest{
|
self.session
|
||||||
|
.mercury(MercuryRequest {
|
||||||
method: MercuryMethod::SEND,
|
method: MercuryMethod::SEND,
|
||||||
uri: format!("hm://remote/user/{}",
|
uri: format!("hm://remote/user/{}",
|
||||||
self.session.0.data.read().unwrap().canonical_username.clone()),
|
self.session.0.data.read().unwrap().canonical_username.clone()),
|
||||||
content_type: None,
|
content_type: None,
|
||||||
payload: vec![ pkt.write_to_bytes().unwrap() ]
|
payload: vec![pkt.write_to_bytes().unwrap()],
|
||||||
}).await().unwrap();
|
})
|
||||||
|
.await()
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spirc_state(&self) -> protocol::spirc::State {
|
fn spirc_state(&self) -> protocol::spirc::State {
|
||||||
|
|
|
@ -17,12 +17,12 @@ type ChannelId = u16;
|
||||||
|
|
||||||
enum ChannelMode {
|
enum ChannelMode {
|
||||||
Header,
|
Header,
|
||||||
Data
|
Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Channel {
|
struct Channel {
|
||||||
mode: ChannelMode,
|
mode: ChannelMode,
|
||||||
callback: mpsc::Sender<StreamEvent>
|
callback: mpsc::Sender<StreamEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamManager {
|
pub struct StreamManager {
|
||||||
|
@ -38,8 +38,11 @@ impl StreamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(&mut self, session: &Session,
|
pub fn request(&mut self,
|
||||||
file: FileId, offset: u32, size: u32)
|
session: &Session,
|
||||||
|
file: FileId,
|
||||||
|
offset: u32,
|
||||||
|
size: u32)
|
||||||
-> mpsc::Receiver<StreamEvent> {
|
-> mpsc::Receiver<StreamEvent> {
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
@ -61,9 +64,10 @@ impl StreamManager {
|
||||||
|
|
||||||
session.send_packet(0x8, &data).unwrap();
|
session.send_packet(0x8, &data).unwrap();
|
||||||
|
|
||||||
self.channels.insert(channel_id, Channel {
|
self.channels.insert(channel_id,
|
||||||
|
Channel {
|
||||||
mode: ChannelMode::Header,
|
mode: ChannelMode::Header,
|
||||||
callback: tx
|
callback: tx,
|
||||||
});
|
});
|
||||||
|
|
||||||
rx
|
rx
|
||||||
|
@ -80,7 +84,9 @@ impl PacketHandler for StreamManager {
|
||||||
{
|
{
|
||||||
let channel = match self.channels.get_mut(&id) {
|
let channel = match self.channels.get_mut(&id) {
|
||||||
Some(ch) => ch,
|
Some(ch) => ch,
|
||||||
None => { return; }
|
None => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match channel.mode {
|
match channel.mode {
|
||||||
|
@ -114,7 +120,8 @@ impl PacketHandler for StreamManager {
|
||||||
ChannelMode::Data => {
|
ChannelMode::Data => {
|
||||||
if packet.position() < data.len() as u64 {
|
if packet.position() < data.len() as u64 {
|
||||||
channel.callback
|
channel.callback
|
||||||
.send(StreamEvent::Data(data.clone().offset(packet.position() as usize)))
|
.send(StreamEvent::Data(data.clone()
|
||||||
|
.offset(packet.position() as usize)))
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
close = true;
|
close = true;
|
||||||
});
|
});
|
||||||
|
@ -130,4 +137,3 @@ impl PacketHandler for StreamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ impl <T> ArcVec<T> {
|
||||||
ArcVec {
|
ArcVec {
|
||||||
data: Arc::new(data),
|
data: Arc::new(data),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: length
|
length: length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,4 +49,3 @@ impl<T : fmt::Debug> fmt::Debug for ArcVec<T> {
|
||||||
self.deref().fmt(formatter)
|
self.deref().fmt(formatter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,15 @@ use std;
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct u128 {
|
pub struct u128 {
|
||||||
high: u64,
|
high: u64,
|
||||||
low: u64
|
low: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl u128 {
|
impl u128 {
|
||||||
pub fn from_parts(high: u64, low: u64) -> u128 {
|
pub fn from_parts(high: u64, low: u64) -> u128 {
|
||||||
u128 { high: high, low: low }
|
u128 {
|
||||||
|
high: high,
|
||||||
|
low: low,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parts(&self) -> (u64, u64) {
|
pub fn parts(&self) -> (u64, u64) {
|
||||||
|
@ -28,7 +31,11 @@ impl std::ops::Add<u128> for u128 {
|
||||||
fn add(self, rhs: u128) -> u128 {
|
fn add(self, rhs: u128) -> u128 {
|
||||||
let low = self.low + rhs.low;
|
let low = self.low + rhs.low;
|
||||||
let high = self.high + rhs.high +
|
let high = self.high + rhs.high +
|
||||||
if low < self.low { 1 } else { 0 };
|
if low < self.low {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
u128::from_parts(high, low)
|
u128::from_parts(high, low)
|
||||||
}
|
}
|
||||||
|
@ -39,7 +46,11 @@ impl <'a> std::ops::Add<&'a u128> for u128 {
|
||||||
fn add(self, rhs: &'a u128) -> u128 {
|
fn add(self, rhs: &'a u128) -> u128 {
|
||||||
let low = self.low + rhs.low;
|
let low = self.low + rhs.low;
|
||||||
let high = self.high + rhs.high +
|
let high = self.high + rhs.high +
|
||||||
if low < self.low { 1 } else { 0 };
|
if low < self.low {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
u128::from_parts(high, low)
|
u128::from_parts(high, low)
|
||||||
}
|
}
|
||||||
|
@ -56,13 +67,15 @@ impl std::ops::Mul<u128> for u128 {
|
||||||
type Output = u128;
|
type Output = u128;
|
||||||
|
|
||||||
fn mul(self, rhs: u128) -> u128 {
|
fn mul(self, rhs: u128) -> u128 {
|
||||||
let top: [u64; 4] =
|
let top: [u64; 4] = [self.high >> 32,
|
||||||
[self.high >> 32, self.high & 0xFFFFFFFF,
|
self.high & 0xFFFFFFFF,
|
||||||
self.low >> 32, self.low & 0xFFFFFFFF];
|
self.low >> 32,
|
||||||
|
self.low & 0xFFFFFFFF];
|
||||||
|
|
||||||
let bottom : [u64; 4] =
|
let bottom: [u64; 4] = [rhs.high >> 32,
|
||||||
[rhs.high >> 32, rhs.high & 0xFFFFFFFF,
|
rhs.high & 0xFFFFFFFF,
|
||||||
rhs.low >> 32, rhs.low & 0xFFFFFFFF];
|
rhs.low >> 32,
|
||||||
|
rhs.low & 0xFFFFFFFF];
|
||||||
|
|
||||||
let mut rows = [std::num::Zero::zero(); 16];
|
let mut rows = [std::num::Zero::zero(); 16];
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
|
@ -76,8 +89,7 @@ impl std::ops::Mul<u128> for u128 {
|
||||||
3 => (product << 32, 0),
|
3 => (product << 32, 0),
|
||||||
_ => {
|
_ => {
|
||||||
if product != 0 {
|
if product != 0 {
|
||||||
panic!("Overflow on mul {:?} {:?} ({} {})",
|
panic!("Overflow on mul {:?} {:?} ({} {})", self, rhs, i, j)
|
||||||
self, rhs, i, j)
|
|
||||||
} else {
|
} else {
|
||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
}
|
||||||
|
@ -90,5 +102,3 @@ impl std::ops::Mul<u128> for u128 {
|
||||||
rows.iter().sum::<u128>()
|
rows.iter().sum::<u128>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
||||||
vec.push(R::rand(rng));
|
vec.push(R::rand(rng));
|
||||||
}
|
}
|
||||||
|
|
||||||
return vec
|
return vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod version {
|
pub mod version {
|
||||||
|
@ -81,11 +81,12 @@ pub fn now_ms() -> i64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mkdir_existing(path: &Path) -> io::Result<()> {
|
pub fn mkdir_existing(path: &Path) -> io::Result<()> {
|
||||||
fs::create_dir(path)
|
fs::create_dir(path).or_else(|err| {
|
||||||
.or_else(|err| if err.kind() == io::ErrorKind::AlreadyExists {
|
if err.kind() == io::ErrorKind::AlreadyExists {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(err)
|
Err(err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,4 +131,3 @@ impl <'s> Iterator for StrChunks<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ pub struct SpotifyId(u128);
|
||||||
#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
|
#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
|
||||||
pub struct FileId(pub [u8; 20]);
|
pub struct FileId(pub [u8; 20]);
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -80,10 +81,10 @@ impl SpotifyId {
|
||||||
|
|
||||||
impl FileId {
|
impl FileId {
|
||||||
pub fn to_base16(&self) -> String {
|
pub fn to_base16(&self) -> String {
|
||||||
self.0.iter()
|
self.0
|
||||||
|
.iter()
|
||||||
.map(|b| format!("{:02x}", b))
|
.map(|b| format!("{:02x}", b))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.concat()
|
.concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Read, Seek, SeekFrom, Result};
|
||||||
|
|
||||||
pub struct Subfile<T: Read + Seek> {
|
pub struct Subfile<T: Read + Seek> {
|
||||||
stream: T,
|
stream: T,
|
||||||
offset: u64
|
offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Read + Seek> Subfile<T> {
|
impl<T: Read + Seek> Subfile<T> {
|
||||||
|
@ -10,7 +10,7 @@ impl <T: Read + Seek> Subfile<T> {
|
||||||
stream.seek(SeekFrom::Start(offset)).unwrap();
|
stream.seek(SeekFrom::Start(offset)).unwrap();
|
||||||
Subfile {
|
Subfile {
|
||||||
stream: stream,
|
stream: stream,
|
||||||
offset: offset
|
offset: offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,16 +25,15 @@ impl <T: Read + Seek> Seek for Subfile<T> {
|
||||||
fn seek(&mut self, mut pos: SeekFrom) -> Result<u64> {
|
fn seek(&mut self, mut pos: SeekFrom) -> Result<u64> {
|
||||||
pos = match pos {
|
pos = match pos {
|
||||||
SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset),
|
SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset),
|
||||||
x => x
|
x => x,
|
||||||
};
|
};
|
||||||
|
|
||||||
let newpos = try!(self.stream.seek(pos));
|
let newpos = try!(self.stream.seek(pos));
|
||||||
|
|
||||||
if newpos > self.offset {
|
if newpos > self.offset {
|
||||||
return Ok(newpos - self.offset)
|
return Ok(newpos - self.offset);
|
||||||
} else {
|
} else {
|
||||||
return Ok(0)
|
return Ok(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@ use std::cmp::{min, max};
|
||||||
|
|
||||||
pub struct ZeroFile {
|
pub struct ZeroFile {
|
||||||
position: u64,
|
position: u64,
|
||||||
size: u64
|
size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZeroFile {
|
impl ZeroFile {
|
||||||
pub fn new(size: u64) -> ZeroFile {
|
pub fn new(size: u64) -> ZeroFile {
|
||||||
ZeroFile {
|
ZeroFile {
|
||||||
position: 0,
|
position: 0,
|
||||||
size: size
|
size: size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,4 +41,3 @@ impl io::Read for ZeroFile {
|
||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ pub mod stub {
|
||||||
_: &[&str])
|
_: &[&str])
|
||||||
-> std::result::Result<DNSService, DNSError> {
|
-> std::result::Result<DNSService, DNSError> {
|
||||||
writeln!(&mut std::io::stderr(),
|
writeln!(&mut std::io::stderr(),
|
||||||
"WARNING: dns-sd is not enabled. Service will probably not be visible").unwrap();
|
"WARNING: dns-sd is not enabled. Service will probably not be visible")
|
||||||
|
.unwrap();
|
||||||
Ok(DNSService)
|
Ok(DNSService)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue