Refactor the whole architecture.

Use less threads, makes it much simpler to reason about.
This commit is contained in:
Paul Lietar 2015-07-02 19:24:25 +02:00
parent c1ce87dbbd
commit 2a2f227bef
10 changed files with 299 additions and 542 deletions

View file

@ -5,22 +5,25 @@ use std::io::{self, SeekFrom};
use std::slice::bytes::copy_memory; use std::slice::bytes::copy_memory;
use std::sync::{Arc, Condvar, Mutex}; use std::sync::{Arc, Condvar, Mutex};
use std::sync::mpsc::{self, TryRecvError}; use std::sync::mpsc::{self, TryRecvError};
use stream::{StreamRequest, StreamEvent};
use util::FileId;
use std::thread; use std::thread;
use stream::StreamEvent;
use util::FileId;
use session::Session;
const CHUNK_SIZE : usize = 0x40000; const CHUNK_SIZE : usize = 0x40000;
#[derive(Clone)] pub struct AudioFile<'s> {
pub struct AudioFile {
position: usize, position: usize,
seek: mpsc::Sender<u64>, seek: mpsc::Sender<u64>,
shared: Arc<AudioFileShared>, shared: Arc<AudioFileShared>,
#[allow(dead_code)]
thread: thread::JoinGuard<'s, ()>,
} }
struct AudioFileShared { struct AudioFileShared {
fileid: FileId, file_id: FileId,
size: usize, size: usize,
data: Mutex<AudioFileData>, data: Mutex<AudioFileData>,
cond: Condvar cond: Condvar
@ -31,38 +34,24 @@ struct AudioFileData {
bitmap: BitSet, bitmap: BitSet,
} }
impl AudioFile { impl <'s> AudioFile <'s> {
pub fn new(fileid: FileId, streams: mpsc::Sender<StreamRequest>) -> AudioFile { pub fn new(session: &Session, file_id: FileId) -> AudioFile {
let (tx, rx) = mpsc::channel(); let mut it = session.stream(file_id, 0, 1).into_iter()
.filter_map(|event| {
streams.send(StreamRequest {
id: fileid,
offset: 0,
size: 1,
callback: tx
}).unwrap();
let size = {
let mut size = None;
for event in rx.iter() {
match event { match event {
StreamEvent::Header(id, data) => { StreamEvent::Header(id, ref data) if id == 0x3 => {
if id == 0x3 { Some(BigEndian::read_u32(data) as usize * 4)
size = Some(BigEndian::read_u32(&data) * 4);
break;
} }
}, _ => None
StreamEvent::Data(_) => break
} }
} });
size.unwrap() as usize
}; let size = it.next().unwrap();
let bufsize = size + (CHUNK_SIZE - size % CHUNK_SIZE); let bufsize = size + (CHUNK_SIZE - size % CHUNK_SIZE);
let (tx, rx) = mpsc::channel();
let shared = Arc::new(AudioFileShared { let shared = Arc::new(AudioFileShared {
fileid: fileid, file_id: file_id,
size: size, size: size,
data: Mutex::new(AudioFileData { data: Mutex::new(AudioFileData {
buffer: vec![0u8; bufsize], buffer: vec![0u8; bufsize],
@ -71,25 +60,23 @@ impl AudioFile {
cond: Condvar::new(), cond: Condvar::new(),
}); });
let file = AudioFile { let shared_ = shared.clone();
position: 0, let (seek_tx, seek_rx) = mpsc::channel();
seek: tx,
shared: shared.clone(),
};
thread::spawn( move || { AudioFile::fetch(shared, streams, rx); }); let file = AudioFile {
thread: thread::scoped( move || { AudioFile::fetch(session, shared_, seek_rx); }),
position: 0,
seek: seek_tx,
shared: shared,
};
file file
} }
fn fetch_chunk(shared: &Arc<AudioFileShared>, streams: &mpsc::Sender<StreamRequest>, index: usize) { fn fetch_chunk(session: &Session, shared: &Arc<AudioFileShared>, index: usize) {
let (tx, rx) = mpsc::channel(); let rx = session.stream(shared.file_id,
streams.send(StreamRequest { (index * CHUNK_SIZE / 4) as u32,
id: shared.fileid, (CHUNK_SIZE / 4) as u32);
offset: (index * CHUNK_SIZE / 4) as u32,
size: (CHUNK_SIZE / 4) as u32,
callback: tx
}).unwrap();
let mut offset = 0usize; let mut offset = 0usize;
for event in rx.iter() { for event in rx.iter() {
@ -114,7 +101,7 @@ impl AudioFile {
} }
} }
fn fetch(shared: Arc<AudioFileShared>, streams: mpsc::Sender<StreamRequest>, seek: mpsc::Receiver<u64>) { fn fetch(session: &Session, shared: Arc<AudioFileShared>, seek: mpsc::Receiver<u64>) {
let mut index = 0; let mut index = 0;
loop { loop {
index = if index * CHUNK_SIZE < shared.size { index = if index * CHUNK_SIZE < shared.size {
@ -138,13 +125,13 @@ impl AudioFile {
} }
if index * CHUNK_SIZE < shared.size { if index * CHUNK_SIZE < shared.size {
AudioFile::fetch_chunk(&shared, &streams, index) AudioFile::fetch_chunk(session, &shared, index)
} }
} }
} }
} }
impl io::Read for AudioFile { impl <'s> io::Read for AudioFile <'s> {
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> { fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
let index = self.position / CHUNK_SIZE; let index = self.position / CHUNK_SIZE;
let offset = self.position % CHUNK_SIZE; let offset = self.position % CHUNK_SIZE;
@ -163,7 +150,7 @@ impl io::Read for AudioFile {
} }
} }
impl io::Seek for AudioFile { impl <'s> io::Seek for AudioFile <'s> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let newpos = match pos { let newpos = match pos {
SeekFrom::Start(offset) => offset as i64, SeekFrom::Start(offset) => offset as i64,

View file

@ -1,109 +1,63 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::mpsc; use std::sync::{mpsc, Future};
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
use readall::ReadAllExt; use readall::ReadAllExt;
use connection::Packet; use util::{SpotifyId, FileId, IgnoreExt};
use util::{SpotifyId, FileId}; use session::Session;
use util::Either::{Left, Right}; use connection::PacketHandler;
use subsystem::Subsystem;
pub struct AudioKeyRequest {
pub track: SpotifyId,
pub file: FileId,
pub callback: AudioKeyCallback,
}
pub type AudioKey = [u8; 16]; pub type AudioKey = [u8; 16];
pub struct AudioKeyResponse(pub AudioKey);
pub type AudioKeyCallback = mpsc::Sender<AudioKeyResponse>;
type AudioKeyId = u32; type AudioKeyId = u32;
pub struct AudioKeyManager { pub struct AudioKeyManager {
next_seq: AudioKeyId, next_seq: AudioKeyId,
callbacks: HashMap<AudioKeyId, AudioKeyCallback>, callbacks: HashMap<AudioKeyId, mpsc::Sender<AudioKey>>,
requests: mpsc::Receiver<AudioKeyRequest>,
packet_rx: mpsc::Receiver<Packet>,
packet_tx: mpsc::Sender<Packet>,
} }
impl AudioKeyManager { impl AudioKeyManager {
pub fn new(tx: mpsc::Sender<Packet>) -> (AudioKeyManager, pub fn new() -> AudioKeyManager {
mpsc::Sender<AudioKeyRequest>, AudioKeyManager {
mpsc::Sender<Packet>) {
let (req_tx, req_rx) = mpsc::channel();
let (pkt_tx, pkt_rx) = mpsc::channel();
(AudioKeyManager {
next_seq: 1, next_seq: 1,
callbacks: HashMap::new(), callbacks: HashMap::new(),
}
requests: req_rx,
packet_rx: pkt_rx,
packet_tx: tx
}, req_tx, pkt_tx)
} }
fn request(&mut self, req: AudioKeyRequest) { pub fn request(&mut self, session: &Session, track: SpotifyId, file: FileId)
-> Future<AudioKey> {
let (tx, rx) = mpsc::channel();
let seq = self.next_seq; let seq = self.next_seq;
self.next_seq += 1; self.next_seq += 1;
let mut data : Vec<u8> = Vec::new(); let mut data : Vec<u8> = Vec::new();
data.write(&req.file).unwrap(); data.write(&file).unwrap();
data.write(&req.track.to_raw()).unwrap(); data.write(&track.to_raw()).unwrap();
data.write_u32::<BigEndian>(seq).unwrap(); data.write_u32::<BigEndian>(seq).unwrap();
data.write_u16::<BigEndian>(0x0000).unwrap(); data.write_u16::<BigEndian>(0x0000).unwrap();
self.packet_tx.send(Packet { session.send_packet(0xc, &data).unwrap();
cmd: 0xc,
data: data
}).unwrap();
self.callbacks.insert(seq, req.callback); self.callbacks.insert(seq, tx);
Future::from_receiver(rx)
}
} }
fn packet(&mut self, packet: Packet) { impl PacketHandler for AudioKeyManager {
assert_eq!(packet.cmd, 0xd); fn handle(&mut self, cmd: u8, data: Vec<u8>) {
assert_eq!(cmd, 0xd);
let mut data = Cursor::new(&packet.data as &[u8]); let mut data = Cursor::new(data);
let seq = data.read_u32::<BigEndian>().unwrap(); let seq = data.read_u32::<BigEndian>().unwrap();
let mut key = [0u8; 16]; let mut key = [0u8; 16];
data.read_all(&mut key).unwrap(); data.read_all(&mut key).unwrap();
match self.callbacks.remove(&seq) { match self.callbacks.remove(&seq) {
Some(callback) => callback.send(AudioKeyResponse(key)).unwrap(), Some(callback) => callback.send(key).ignore(),
None => () None => ()
}; };
} }
} }
impl Subsystem for AudioKeyManager {
fn run(mut self) {
loop {
match {
let requests = &self.requests;
let packets = &self.packet_rx;
select!{
r = requests.recv() => {
Left(r.unwrap())
},
p = packets.recv() => {
Right(p.unwrap())
}
}
} {
Left(req) => {
self.request(req);
}
Right(pkt) => {
self.packet(pkt);
}
}
}
}
}

View file

@ -6,7 +6,6 @@ use std::io;
use std::io::Write; use std::io::Write;
use std::net::TcpStream; use std::net::TcpStream;
use std::result; use std::result;
use std::sync::mpsc;
use keys::SharedKeys; use keys::SharedKeys;
@ -84,7 +83,7 @@ impl PlainConnection {
} }
impl CipherConnection { impl CipherConnection {
pub fn send_encrypted_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));
@ -107,70 +106,15 @@ impl CipherConnection {
} }
} }
pub struct Packet { pub trait PacketHandler {
pub cmd: u8, fn handle(&mut self, cmd: u8, data: Vec<u8>);
pub data: Vec<u8>
} }
pub struct SendThread { /*
connection: CipherConnection,
receiver: mpsc::Receiver<Packet>,
}
impl SendThread {
pub fn new(connection: CipherConnection)
-> (SendThread, mpsc::Sender<Packet>) {
let (tx, rx) = mpsc::channel();
(SendThread {
connection: connection,
receiver: rx
}, tx)
}
pub fn run(mut self) {
for req in self.receiver {
self.connection.send_encrypted_packet(
req.cmd, &req.data).unwrap();
}
}
}
pub struct PacketDispatch {
pub main: mpsc::Sender<Packet>,
pub stream: mpsc::Sender<Packet>,
pub mercury: mpsc::Sender<Packet>,
pub audio_key: mpsc::Sender<Packet>,
}
pub struct RecvThread {
connection: CipherConnection,
dispatch: PacketDispatch
}
impl RecvThread {
pub fn new(connection: CipherConnection, dispatch: PacketDispatch)
-> RecvThread {
RecvThread {
connection: connection,
dispatch: dispatch
}
}
pub fn run(mut self) {
loop {
let (cmd, data) = self.connection.recv_packet().unwrap();
let packet = Packet {
cmd: cmd,
data: data
};
match packet.cmd { match packet.cmd {
0x09 => &self.dispatch.stream, 0x09 => &self.dispatch.stream,
0xd | 0xe => &self.dispatch.audio_key, 0xd | 0xe => &self.dispatch.audio_key,
0xb2...0xb6 => &self.dispatch.mercury, 0xb2...0xb6 => &self.dispatch.mercury,
_ => &self.dispatch.main, _ => &self.dispatch.main,
}.send(packet).unwrap(); }.send(packet).unwrap();
*/
}
}
}

View file

@ -1,7 +1,8 @@
#![crate_name = "librespot"] #![crate_name = "librespot"]
#![feature(plugin,zero_one,iter_arith,slice_position_elem,slice_bytes,bitset,mpsc_select,arc_weak,append)] #![feature(plugin,scoped,zero_one,iter_arith,slice_position_elem,slice_bytes,bitset,arc_weak,append,future)]
#![allow(unused_imports,dead_code)] #![allow(deprecated)]
//#![allow(unused_imports,dead_code)]
#![plugin(protobuf_macros)] #![plugin(protobuf_macros)]
#[macro_use] extern crate lazy_static; #[macro_use] extern crate lazy_static;
@ -38,10 +39,10 @@ use std::clone::Clone;
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::Path; use std::path::Path;
use std::sync::mpsc;
use protobuf::core::Message; use protobuf::core::Message;
use std::thread;
use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef}; use metadata::{AlbumRef, ArtistRef, TrackRef};
use session::{Config, Session}; use session::{Config, Session};
use util::SpotifyId; use util::SpotifyId;
use util::version::version_string; use util::version::version_string;
@ -69,12 +70,17 @@ fn main() {
session.login(username.clone(), password); session.login(username.clone(), password);
session.poll(); session.poll();
let ident = session.config.device_id.clone(); let poll_thread = thread::scoped(|| {
loop {
session.poll();
}
});
SpircManager { SpircManager {
session: session, session: &session,
username: username.clone(), username: username.clone(),
name: name.clone(), name: name.clone(),
ident: ident, ident: session.config.device_id.clone(),
device_type: 5, device_type: 5,
state_update_id: 0, state_update_id: 0,
@ -88,21 +94,17 @@ fn main() {
state: PlayerState::new() state: PlayerState::new()
}.run(); }.run();
/* poll_thread.join();
loop {
session.poll();
}
*/
} }
fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) { fn print_track(session: &Session, track_id: SpotifyId) {
let track : TrackRef = cache.get(track_id); let track : TrackRef = session.metadata(track_id);
let album : AlbumRef = { let album : AlbumRef = {
let handle = track.wait(); let handle = track.wait();
let data = handle.unwrap(); let data = handle.unwrap();
eprintln!("{}", data.name); eprintln!("{}", data.name);
cache.get(data.album) session.metadata(data.album)
}; };
let artists : Vec<ArtistRef> = { let artists : Vec<ArtistRef> = {
@ -110,7 +112,7 @@ fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
let data = handle.unwrap(); let data = handle.unwrap();
eprintln!("{}", data.name); eprintln!("{}", data.name);
data.artists.iter().map(|id| { data.artists.iter().map(|id| {
cache.get(*id) session.metadata(*id)
}).collect() }).collect()
}; };
@ -159,7 +161,6 @@ impl PlayerState {
} }
fn import(&mut self, state: &protocol::spirc::State) { fn import(&mut self, state: &protocol::spirc::State) {
//println!("{:?}", state);
self.status = state.get_status(); self.status = state.get_status();
self.context_uri = state.get_context_uri().to_string(); self.context_uri = state.get_context_uri().to_string();
@ -203,8 +204,8 @@ impl PlayerState {
} }
} }
struct SpircManager { struct SpircManager<'s> {
session: Session, session: &'s Session,
username: String, username: String,
state_update_id: i64, state_update_id: i64,
seq_nr: u32, seq_nr: u32,
@ -221,24 +222,16 @@ struct SpircManager {
state: PlayerState state: PlayerState
} }
impl SpircManager { impl <'s> SpircManager<'s> {
fn run(&mut self) { fn run(&mut self) {
let (tx, rx) = mpsc::channel(); let rx = self.session
.mercury_sub(format!("hm://remote/user/{}/v23", self.username))
self.session.mercury.send(MercuryRequest{ .into_iter().map(|pkt| {
method: MercuryMethod::SUB,
uri: format!("hm://remote/user/{}/v23", self.username),
content_type: None,
callback: Some(tx),
payload: Vec::new()
}).unwrap();
self.notify(None);
let rx = rx.into_iter().map(|pkt| {
protobuf::parse_from_bytes::<protocol::spirc::Frame>(pkt.payload.front().unwrap()).unwrap() protobuf::parse_from_bytes::<protocol::spirc::Frame>(pkt.payload.front().unwrap()).unwrap()
}); });
self.notify(None);
for frame in rx { for frame in rx {
println!("{:?} {} {} {} {}", println!("{:?} {} {} {} {}",
frame.get_typ(), frame.get_typ(),
@ -328,13 +321,12 @@ impl SpircManager {
pkt.set_state(self.state.export()); pkt.set_state(self.state.export());
} }
self.session.mercury.send(MercuryRequest{ self.session.mercury(MercuryRequest{
method: MercuryMethod::SEND, method: MercuryMethod::SEND,
uri: format!("hm://remote/user/{}", self.username), uri: format!("hm://remote/user/{}", self.username),
content_type: None, content_type: None,
callback: None,
payload: vec![ pkt.write_to_bytes().unwrap() ] payload: vec![ pkt.write_to_bytes().unwrap() ]
}).unwrap(); });
} }
fn device_state(&mut self) -> protocol::spirc::DeviceState { fn device_state(&mut self) -> protocol::spirc::DeviceState {

View file

@ -5,12 +5,12 @@ use std::collections::{HashMap, LinkedList};
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use std::fmt; use std::fmt;
use std::mem::replace; use std::mem::replace;
use std::sync::mpsc; use std::sync::{mpsc, Future};
use connection::Packet;
use librespot_protocol as protocol; use librespot_protocol as protocol;
use subsystem::Subsystem; use session::Session;
use util::Either::{Left, Right}; use connection::PacketHandler;
use util::IgnoreExt;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum MercuryMethod { pub enum MercuryMethod {
@ -24,7 +24,6 @@ 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 callback: Option<MercuryCallback>,
pub payload: Vec<Vec<u8>> pub payload: Vec<Vec<u8>>
} }
@ -34,22 +33,16 @@ pub struct MercuryResponse {
pub payload: LinkedList<Vec<u8>> pub payload: LinkedList<Vec<u8>>
} }
pub type MercuryCallback = mpsc::Sender<MercuryResponse>;
pub struct MercuryPending { pub struct MercuryPending {
parts: LinkedList<Vec<u8>>, parts: LinkedList<Vec<u8>>,
partial: Option<Vec<u8>>, partial: Option<Vec<u8>>,
callback: Option<MercuryCallback> callback: Option<mpsc::Sender<MercuryResponse>>
} }
pub struct MercuryManager { pub struct MercuryManager {
next_seq: u32, next_seq: u32,
pending: HashMap<Vec<u8>, MercuryPending>, pending: HashMap<Vec<u8>, MercuryPending>,
subscriptions: HashMap<String, MercuryCallback>, subscriptions: HashMap<String, mpsc::Sender<MercuryResponse>>,
requests: mpsc::Receiver<MercuryRequest>,
packet_tx: mpsc::Sender<Packet>,
packet_rx: mpsc::Receiver<Packet>,
} }
impl fmt::Display for MercuryMethod { impl fmt::Display for MercuryMethod {
@ -64,24 +57,17 @@ impl fmt::Display for MercuryMethod {
} }
impl MercuryManager { impl MercuryManager {
pub fn new(tx: mpsc::Sender<Packet>) -> (MercuryManager, pub fn new() -> MercuryManager {
mpsc::Sender<MercuryRequest>, MercuryManager {
mpsc::Sender<Packet>) {
let (req_tx, req_rx) = mpsc::channel();
let (pkt_tx, pkt_rx) = mpsc::channel();
(MercuryManager {
next_seq: 0, next_seq: 0,
pending: HashMap::new(), pending: HashMap::new(),
subscriptions: HashMap::new(), subscriptions: HashMap::new(),
}
requests: req_rx,
packet_rx: pkt_rx,
packet_tx: tx,
}, req_tx, pkt_tx)
} }
fn request(&mut self, req: MercuryRequest) { pub fn request(&mut self, session: &Session, req: MercuryRequest)
-> Future<MercuryResponse> {
let mut seq = [0u8; 4]; let mut seq = [0u8; 4];
BigEndian::write_u32(&mut seq, self.next_seq); BigEndian::write_u32(&mut seq, self.next_seq);
self.next_seq += 1; self.next_seq += 1;
@ -93,20 +79,31 @@ impl MercuryManager {
_ => 0xb2, _ => 0xb2,
}; };
self.packet_tx.send(Packet { session.send_packet(cmd, &data).unwrap();
cmd: cmd,
data: data
}).unwrap();
if req.method != MercuryMethod::SUB { let (tx, rx) = mpsc::channel();
self.pending.insert(seq.to_vec(), MercuryPending{ self.pending.insert(seq.to_vec(), MercuryPending{
parts: LinkedList::new(), parts: LinkedList::new(),
partial: None, partial: None,
callback: req.callback, callback: Some(tx),
}); });
} else if let Some(cb) = req.callback {
self.subscriptions.insert(req.uri, cb); Future::from_receiver(rx)
} }
pub fn subscribe(&mut self, session: &Session, uri: String)
-> mpsc::Receiver<MercuryResponse> {
let (tx, rx) = mpsc::channel();
self.subscriptions.insert(uri.clone(), tx);
self.request(session, MercuryRequest{
method: MercuryMethod::SUB,
uri: uri,
content_type: None,
payload: Vec::new()
});
rx
} }
fn parse_part(mut s: &mut Read) -> Vec<u8> { fn parse_part(mut s: &mut Read) -> Vec<u8> {
@ -133,14 +130,44 @@ impl MercuryManager {
}; };
if let Some(ref ch) = callback { if let Some(ref ch) = callback {
// Ignore send error.
// It simply means the receiver was closed
ch.send(MercuryResponse{ ch.send(MercuryResponse{
uri: header.get_uri().to_string(), uri: header.get_uri().to_string(),
payload: pending.parts payload: pending.parts
}).unwrap(); }).ignore();
} }
} }
fn handle_packet(&mut self, cmd: u8, data: Vec<u8>) { fn encode_request(&self, seq: &[u8], req: &MercuryRequest) -> Vec<u8> {
let mut packet = Vec::new();
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
packet.write_all(seq).unwrap();
packet.write_u8(1).unwrap(); // Flags: FINAL
packet.write_u16::<BigEndian>(1 + req.payload.len() as u16).unwrap(); // Part count
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
uri: req.uri.clone(),
method: req.method.to_string(),
});
if let Some(ref content_type) = req.content_type {
header.set_content_type(content_type.clone());
}
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
header.write_to_writer(&mut packet).unwrap();
for p in &req.payload {
packet.write_u16::<BigEndian>(p.len() as u16).unwrap();
packet.write(&p).unwrap();
}
packet
}
}
impl PacketHandler for MercuryManager {
fn handle(&mut self, cmd: u8, data: Vec<u8>) {
let mut packet = Cursor::new(data); let mut packet = Cursor::new(data);
let seq = { let seq = {
@ -185,59 +212,5 @@ impl MercuryManager {
self.pending.insert(seq, pending); self.pending.insert(seq, pending);
} }
} }
fn encode_request(&self, seq: &[u8], req: &MercuryRequest) -> Vec<u8> {
let mut packet = Vec::new();
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
packet.write_all(seq).unwrap();
packet.write_u8(1).unwrap(); // Flags: FINAL
packet.write_u16::<BigEndian>(1 + req.payload.len() as u16).unwrap(); // Part count
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
uri: req.uri.clone(),
method: req.method.to_string(),
});
if let Some(ref content_type) = req.content_type {
header.set_content_type(content_type.clone());
}
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
header.write_to_writer(&mut packet).unwrap();
for p in &req.payload {
packet.write_u16::<BigEndian>(p.len() as u16).unwrap();
packet.write(&p).unwrap();
}
packet
}
}
impl Subsystem for MercuryManager {
fn run(mut self) {
loop {
match {
let requests = &self.requests;
let packets = &self.packet_rx;
select!{
r = requests.recv() => {
Left(r.unwrap())
},
p = packets.recv() => {
Right(p.unwrap())
}
}
} {
Left(req) => {
self.request(req);
}
Right(pkt) => {
self.handle_packet(pkt.cmd, pkt.data);
}
}
}
}
} }

View file

@ -3,13 +3,13 @@ use std::any::{Any, TypeId};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::slice::bytes::copy_memory; use std::slice::bytes::copy_memory;
use std::sync::{mpsc, Arc, Condvar, Mutex, MutexGuard, Weak}; use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
use std::thread; use std::thread;
use librespot_protocol as protocol; use librespot_protocol as protocol;
use mercury::{MercuryRequest, MercuryMethod}; use mercury::{MercuryRequest, MercuryMethod};
use subsystem::Subsystem;
use util::{SpotifyId, FileId}; use util::{SpotifyId, FileId};
use session::Session;
pub trait MetadataTrait : Send + Any + 'static { pub trait MetadataTrait : Send + Any + 'static {
type Message: protobuf::MessageStatic; type Message: protobuf::MessageStatic;
@ -119,40 +119,6 @@ pub type TrackRef = MetadataRef<Track>;
pub type AlbumRef = MetadataRef<Album>; pub type AlbumRef = MetadataRef<Album>;
pub type ArtistRef = MetadataRef<Artist>; pub type ArtistRef = MetadataRef<Artist>;
pub struct MetadataCache {
metadata: mpsc::Sender<MetadataRequest>,
cache: HashMap<(SpotifyId, TypeId), Box<Any + 'static>>
}
impl MetadataCache {
pub fn new(metadata: mpsc::Sender<MetadataRequest>) -> MetadataCache {
MetadataCache {
metadata: metadata,
cache: HashMap::new()
}
}
pub fn get<T: MetadataTrait>(&mut self, id: SpotifyId)
-> MetadataRef<T> {
let key = (id, TypeId::of::<T>());
self.cache.get(&key)
.and_then(|x| x.downcast_ref::<Weak<Metadata<T>>>())
.and_then(|x| x.upgrade())
.unwrap_or_else(|| {
let x : MetadataRef<T> = Arc::new(Metadata{
id: id,
state: Mutex::new(MetadataState::Loading),
cond: Condvar::new()
});
self.cache.insert(key, Box::new(x.downgrade()));
self.metadata.send(T::request(x.clone())).unwrap();
x
})
}
}
impl <T: MetadataTrait> Metadata<T> { impl <T: MetadataTrait> Metadata<T> {
pub fn id(&self) -> SpotifyId { pub fn id(&self) -> SpotifyId {
self.id self.id
@ -214,34 +180,46 @@ pub enum MetadataRequest {
} }
pub struct MetadataManager { pub struct MetadataManager {
requests: mpsc::Receiver<MetadataRequest>, cache: HashMap<(SpotifyId, TypeId), Box<Any + Send>>
mercury: mpsc::Sender<MercuryRequest>
} }
impl MetadataManager { impl MetadataManager {
pub fn new(mercury: mpsc::Sender<MercuryRequest>) -> (MetadataManager, pub fn new() -> MetadataManager {
mpsc::Sender<MetadataRequest>) { MetadataManager {
let (tx, rx) = mpsc::channel(); cache: HashMap::new()
(MetadataManager { }
requests: rx,
mercury: mercury
}, tx)
} }
fn load<T: MetadataTrait> (&self, object: MetadataRef<T>) { pub fn get<T: MetadataTrait>(&mut self, session: &Session, id: SpotifyId)
let mercury = self.mercury.clone(); -> MetadataRef<T> {
thread::spawn(move || { let key = (id, TypeId::of::<T>());
let (tx, rx) = mpsc::channel();
mercury.send(MercuryRequest { self.cache.get(&key)
.and_then(|x| x.downcast_ref::<Weak<Metadata<T>>>())
.and_then(|x| x.upgrade())
.unwrap_or_else(|| {
let x : MetadataRef<T> = Arc::new(Metadata{
id: id,
state: Mutex::new(MetadataState::Loading),
cond: Condvar::new()
});
self.cache.insert(key, Box::new(x.downgrade()));
self.load(session, x.clone());
x
})
}
fn load<T: MetadataTrait> (&self, session: &Session, object: MetadataRef<T>) {
let rx = session.mercury(MercuryRequest {
method: MercuryMethod::GET, method: MercuryMethod::GET,
uri: format!("{}/{}", T::base_url(), object.id.to_base16()), uri: format!("{}/{}", T::base_url(), object.id.to_base16()),
content_type: None, content_type: None,
callback: Some(tx),
payload: Vec::new() payload: Vec::new()
}).unwrap(); });
let response = rx.recv().unwrap(); thread::spawn(move || {
let response = rx.into_inner();
let msg : T::Message = protobuf::parse_from_bytes( let msg : T::Message = protobuf::parse_from_bytes(
response.payload.front().unwrap()).unwrap(); response.payload.front().unwrap()).unwrap();
@ -251,21 +229,3 @@ impl MetadataManager {
} }
} }
impl Subsystem for MetadataManager {
fn run(self) {
for req in self.requests.iter() {
match req {
MetadataRequest::Artist(artist) => {
self.load(artist)
}
MetadataRequest::Album(album) => {
self.load(album)
}
MetadataRequest::Track(track) => {
self.load(track)
}
}
}
}
}

View file

@ -1,8 +1,6 @@
use portaudio; use portaudio;
use std::sync::mpsc;
use vorbis; use vorbis;
use audio_key::{AudioKeyRequest, AudioKeyResponse};
use metadata::TrackRef; use metadata::TrackRef;
use session::Session; use session::Session;
use audio_file::AudioFile; use audio_file::AudioFile;
@ -15,24 +13,13 @@ impl Player {
pub fn play(session: &Session, track: TrackRef) { pub fn play(session: &Session, track: TrackRef) {
let file_id = *track.wait().unwrap().files.first().unwrap(); let file_id = *track.wait().unwrap().files.first().unwrap();
let key = { let key = session.audio_key(track.id(), file_id).into_inner();
let (tx, rx) = mpsc::channel();
session.audio_key.send(AudioKeyRequest {
track: track.id(),
file: file_id,
callback: tx
}).unwrap();
let AudioKeyResponse(key) = rx.recv().unwrap();
key
};
let mut decoder = let mut decoder =
vorbis::Decoder::new( vorbis::Decoder::new(
Subfile::new( Subfile::new(
AudioDecrypt::new(key, AudioDecrypt::new(key,
AudioFile::new(file_id, session.stream.clone())), 0xa7)).unwrap(); AudioFile::new(session, file_id)), 0xa7)).unwrap();
//decoder.time_seek(60f64).unwrap(); //decoder.time_seek(60f64).unwrap();
portaudio::initialize().unwrap(); portaudio::initialize().unwrap();

View file

@ -2,17 +2,19 @@ use crypto::digest::Digest;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use protobuf::{self, Message}; use protobuf::{self, Message};
use rand::thread_rng; use rand::thread_rng;
use std::sync::mpsc; use std::sync::{Mutex, Arc, Future, mpsc};
use std::thread;
use audio_key; use connection::{self, PlainConnection, CipherConnection};
use connection::{PlainConnection, Packet, PacketDispatch, SendThread, RecvThread};
use keys::PrivateKeys; use keys::PrivateKeys;
use librespot_protocol as protocol; use librespot_protocol as protocol;
use mercury; use util::{SpotifyId, FileId};
use metadata;
use stream; use mercury::{MercuryManager, MercuryRequest, MercuryResponse};
use subsystem::Subsystem; use metadata::{MetadataManager, MetadataRef, MetadataTrait};
use stream::{StreamManager, StreamEvent};
use audio_key::{AudioKeyManager, AudioKey};
use connection::PacketHandler;
use util; use util;
pub struct Config { pub struct Config {
@ -24,15 +26,16 @@ pub struct Config {
pub struct Session { pub struct Session {
pub config: Config, pub config: Config,
packet_rx: mpsc::Receiver<Packet>, mercury: Mutex<MercuryManager>,
pub packet_tx: mpsc::Sender<Packet>, metadata: Mutex<MetadataManager>,
stream: Mutex<StreamManager>,
pub audio_key: mpsc::Sender<audio_key::AudioKeyRequest>, audio_key: Mutex<AudioKeyManager>,
pub mercury: mpsc::Sender<mercury::MercuryRequest>, rx_connection: Mutex<CipherConnection>,
pub metadata: mpsc::Sender<metadata::MetadataRequest>, tx_connection: Mutex<CipherConnection>,
pub stream: mpsc::Sender<stream::StreamRequest>,
} }
type SessionRef = Arc<Session>;
impl Session { impl Session {
pub fn new(mut config: Config) -> Session { pub fn new(mut config: Config) -> Session {
config.device_id = { config.device_id = {
@ -105,41 +108,16 @@ impl Session {
let cipher_connection = connection.setup_cipher(shared_keys); let cipher_connection = connection.setup_cipher(shared_keys);
let (send_thread, tx) = SendThread::new(cipher_connection.clone());
let (main_tx, rx) = mpsc::channel();
let (mercury, mercury_req, mercury_pkt)
= mercury::MercuryManager::new(tx.clone());
let (metadata, metadata_req)
= metadata::MetadataManager::new(mercury_req.clone());
let (stream, stream_req, stream_pkt)
= stream::StreamManager::new(tx.clone());
let (audio_key, audio_key_req, audio_key_pkt)
= audio_key::AudioKeyManager::new(tx.clone());
let recv_thread = RecvThread::new(cipher_connection, PacketDispatch {
main: main_tx,
stream: stream_pkt,
mercury: mercury_pkt,
audio_key: audio_key_pkt
});
thread::spawn(move || send_thread.run());
thread::spawn(move || recv_thread.run());
mercury.start();
metadata.start();
stream.start();
audio_key.start();
Session { Session {
config: config, config: config,
packet_rx: rx,
packet_tx: tx, rx_connection: Mutex::new(cipher_connection.clone()),
mercury: mercury_req, tx_connection: Mutex::new(cipher_connection),
metadata: metadata_req,
stream: stream_req, mercury: Mutex::new(MercuryManager::new()),
audio_key: audio_key_req, metadata: Mutex::new(MetadataManager::new()),
stream: Mutex::new(StreamManager::new()),
audio_key: Mutex::new(AudioKeyManager::new()),
} }
} }
@ -166,32 +144,47 @@ impl Session {
} }
}); });
self.packet_tx.send(Packet { self.send_packet(0xab, &packet.write_to_bytes().unwrap()).unwrap();
cmd: 0xab,
data: packet.write_to_bytes().unwrap()
}).unwrap();
} }
pub fn poll(&self) { pub fn poll(&self) {
let packet = self.packet_rx.recv().unwrap(); let (cmd, data) =
self.rx_connection.lock().unwrap().recv_packet().unwrap();
match packet.cmd { match cmd {
0x4 => { // PING 0x4 => self.send_packet(0x49, &data).unwrap(),
self.packet_tx.send(Packet { 0x4a => (),
cmd: 0x49, 0x9 => self.stream.lock().unwrap().handle(cmd, data),
data: packet.data 0xd | 0xe => self.audio_key.lock().unwrap().handle(cmd, data),
}).unwrap(); 0xb2...0xb6 => self.mercury.lock().unwrap().handle(cmd, data),
} 0xac => eprintln!("Authentication succeedded"),
0x4a => { // PONG 0xad => eprintln!("Authentication failed"),
}
0xac => { // AUTHENTICATED
eprintln!("Authentication succeedded");
}
0xad => {
eprintln!("Authentication failed");
}
_ => () _ => ()
}; }
}
pub fn send_packet(&self, cmd: u8, data: &[u8]) -> connection::Result<()> {
self.tx_connection.lock().unwrap().send_packet(cmd, data)
}
pub fn audio_key(&self, track: SpotifyId, file: FileId) -> Future<AudioKey> {
self.audio_key.lock().unwrap().request(self, track, file)
}
pub fn stream(&self, file: FileId, offset: u32, size: u32) -> mpsc::Receiver<StreamEvent> {
self.stream.lock().unwrap().request(self, file, offset, size)
}
pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> {
self.metadata.lock().unwrap().get(self, id)
}
pub fn mercury(&self, req: MercuryRequest) -> Future<MercuryResponse> {
self.mercury.lock().unwrap().request(self, req)
}
pub fn mercury_sub(&self, uri: String) -> mpsc::Receiver<MercuryResponse> {
self.mercury.lock().unwrap().subscribe(self, uri)
} }
} }

View file

@ -3,18 +3,9 @@ use std::collections::HashMap;
use std::io::{Cursor, Seek, SeekFrom, Write}; use std::io::{Cursor, Seek, SeekFrom, Write};
use std::sync::mpsc; use std::sync::mpsc;
use connection::Packet;
use util::{ArcVec, FileId}; use util::{ArcVec, FileId};
use util::Either::{Left, Right}; use connection::PacketHandler;
use subsystem::Subsystem; use session::Session;
pub type StreamCallback = mpsc::Sender<StreamEvent>;
pub struct StreamRequest {
pub id: FileId,
pub offset: u32,
pub size: u32,
pub callback: StreamCallback
}
#[derive(Debug)] #[derive(Debug)]
pub enum StreamEvent { pub enum StreamEvent {
@ -31,36 +22,28 @@ enum ChannelMode {
struct Channel { struct Channel {
mode: ChannelMode, mode: ChannelMode,
callback: StreamCallback callback: mpsc::Sender<StreamEvent>
} }
pub struct StreamManager { pub struct StreamManager {
next_id: ChannelId, next_id: ChannelId,
channels: HashMap<ChannelId, Channel>, channels: HashMap<ChannelId, Channel>,
requests: mpsc::Receiver<StreamRequest>,
packet_rx: mpsc::Receiver<Packet>,
packet_tx: mpsc::Sender<Packet>,
} }
impl StreamManager { impl StreamManager {
pub fn new(tx: mpsc::Sender<Packet>) -> (StreamManager, pub fn new() -> StreamManager {
mpsc::Sender<StreamRequest>, StreamManager {
mpsc::Sender<Packet>) {
let (req_tx, req_rx) = mpsc::channel();
let (pkt_tx, pkt_rx) = mpsc::channel();
(StreamManager {
next_id: 0, next_id: 0,
channels: HashMap::new(), channels: HashMap::new(),
}
requests: req_rx,
packet_rx: pkt_rx,
packet_tx: tx
}, req_tx, pkt_tx)
} }
fn request(&mut self, req: StreamRequest) { pub fn request(&mut self, session: &Session,
file: FileId, offset: u32, size: u32)
-> mpsc::Receiver<StreamEvent> {
let (tx, rx) = mpsc::channel();
let channel_id = self.next_id; let channel_id = self.next_id;
self.next_id += 1; self.next_id += 1;
@ -72,22 +55,23 @@ impl StreamManager {
data.write_u32::<BigEndian>(0x00000000).unwrap(); data.write_u32::<BigEndian>(0x00000000).unwrap();
data.write_u32::<BigEndian>(0x00009C40).unwrap(); data.write_u32::<BigEndian>(0x00009C40).unwrap();
data.write_u32::<BigEndian>(0x00020000).unwrap(); data.write_u32::<BigEndian>(0x00020000).unwrap();
data.write(&req.id).unwrap(); data.write(&file).unwrap();
data.write_u32::<BigEndian>(req.offset).unwrap(); data.write_u32::<BigEndian>(offset).unwrap();
data.write_u32::<BigEndian>(req.offset + req.size).unwrap(); data.write_u32::<BigEndian>(offset + size).unwrap();
self.packet_tx.send(Packet { session.send_packet(0x8, &data).unwrap();
cmd: 0x8,
data: data
}).unwrap();
self.channels.insert(channel_id, Channel { self.channels.insert(channel_id, Channel {
mode: ChannelMode::Header, mode: ChannelMode::Header,
callback: req.callback callback: tx
}); });
rx
}
} }
fn packet(&mut self, data: Vec<u8>) { impl PacketHandler for StreamManager {
fn handle(&mut self, _cmd: u8, data: Vec<u8>) {
let data = ArcVec::new(data); let data = ArcVec::new(data);
let mut packet = Cursor::new(&data as &[u8]); let mut packet = Cursor::new(&data as &[u8]);
@ -140,27 +124,3 @@ impl StreamManager {
} }
} }
impl Subsystem for StreamManager {
fn run(mut self) {
loop {
match {
let requests = &self.requests;
let packets = &self.packet_rx;
select!{
r = requests.recv() => {
Left(r.unwrap())
},
p = packets.recv() => {
Right(p.unwrap())
}
}
} {
Left(req) => self.request(req),
Right(pkt) => self.packet(pkt.data)
}
}
}
}

View file

@ -47,11 +47,6 @@ pub mod version {
} }
} }
pub enum Either<S,T> {
Left(S),
Right(T)
}
pub fn hexdump(data: &[u8]) { pub fn hexdump(data: &[u8]) {
for b in data.iter() { for b in data.iter() {
eprint!("{:02X} ", b); eprint!("{:02X} ", b);
@ -59,3 +54,15 @@ pub fn hexdump(data: &[u8]) {
eprintln!(""); eprintln!("");
} }
pub trait IgnoreExt {
fn ignore(self);
}
impl <T, E> IgnoreExt for Result<T, E> {
fn ignore(self) {
match self {
Ok(_) => (),
Err(_) => (),
}
}
}