This commit is contained in:
Paul Lietar 2016-01-02 16:19:39 +01:00
parent 5464647164
commit 90eeed3f80
22 changed files with 380 additions and 342 deletions

View file

@ -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)
} }
} }

View file

@ -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)),
} }
} }
} }

View file

@ -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 {
} }
} }
} }

View file

@ -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 {

View file

@ -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>);
} }

View file

@ -48,4 +48,3 @@ impl DHLocalKeys {
shared_key.to_bytes_be() shared_key.to_bytes_be()
} }
} }

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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();
} }
} }

View file

@ -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 {
} }
} }
} }

View file

@ -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))
}) })
} }
} }

View file

@ -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;
} }
} }

View file

@ -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)
} }
} }

View file

@ -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 {

View file

@ -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 {
} }
} }
} }

View file

@ -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)
} }
} }

View file

@ -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>()
} }
} }

View file

@ -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> {
} }
} }
} }

View file

@ -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()
} }
} }

View file

@ -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);
} }
} }
} }

View file

@ -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)
} }
} }

View file

@ -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)
} }
} }