Support downloading Album Covers.

This commit is contained in:
Paul Lietar 2016-03-13 20:03:40 +00:00
parent aa1d466e92
commit 4d712efb48
5 changed files with 97 additions and 54 deletions

27
src/album_cover.rs Normal file
View file

@ -0,0 +1,27 @@
use eventual;
use std::io::Write;
use byteorder::{WriteBytesExt, BigEndian};
use session::Session;
use util::FileId;
use stream::StreamEvent;
pub fn get_album_cover(session: &Session, file_id: FileId)
-> eventual::Future<Vec<u8>, ()> {
let (channel_id, rx) = session.allocate_stream();
let mut req: Vec<u8> = Vec::new();
req.write_u16::<BigEndian>(channel_id).unwrap();
req.write_u16::<BigEndian>(0).unwrap();
req.write(&file_id.0).unwrap();
session.send_packet(0x19, &req).unwrap();
rx.map_err(|_| ())
.reduce(Vec::new(), |mut current, event| {
if let StreamEvent::Data(data) = event {
current.extend_from_slice(&data)
}
current
})
}

View file

@ -40,7 +40,7 @@ struct AudioFileShared {
impl AudioFileLoading { impl AudioFileLoading {
fn new(session: &Session, file_id: FileId) -> AudioFileLoading { fn new(session: &Session, file_id: FileId) -> AudioFileLoading {
let size = session.stream(file_id, 0, 1) let size = session.stream(file_id, 0, 1)
.into_iter() .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 => {

View file

@ -1,4 +1,5 @@
#[macro_use] pub mod util; #[macro_use] pub mod util;
mod album_cover;
mod audio_decrypt; mod audio_decrypt;
mod audio_file; mod audio_file;
mod audio_key; mod audio_key;
@ -13,6 +14,8 @@ pub mod player;
pub mod session; pub mod session;
pub mod spirc; pub mod spirc;
pub mod link; pub mod link;
mod stream; pub mod stream;
pub mod apresolve; pub mod apresolve;
mod zeroconf; mod zeroconf;
pub use album_cover::get_album_cover;

View file

@ -2,6 +2,7 @@ use crypto::digest::Digest;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use crypto::hmac::Hmac; use crypto::hmac::Hmac;
use crypto::mac::Mac; use crypto::mac::Mac;
use eventual;
use eventual::Future; use eventual::Future;
use protobuf::{self, Message}; use protobuf::{self, Message};
use rand::thread_rng; use rand::thread_rng;
@ -20,10 +21,9 @@ use diffie_hellman::DHLocalKeys;
use mercury::{MercuryManager, MercuryRequest, MercuryResponse}; use mercury::{MercuryManager, MercuryRequest, MercuryResponse};
use metadata::{MetadataManager, MetadataRef, MetadataTrait}; use metadata::{MetadataManager, MetadataRef, MetadataTrait};
use protocol; use protocol;
use stream::{StreamManager, StreamEvent}; use stream::{ChannelId, StreamManager, StreamEvent, StreamError};
use util::{self, SpotifyId, FileId, mkdir_existing}; use util::{self, SpotifyId, FileId, mkdir_existing};
pub enum Bitrate { pub enum Bitrate {
Bitrate96, Bitrate96,
Bitrate160, Bitrate160,
@ -249,7 +249,7 @@ impl Session {
match cmd { match cmd {
0x4 => self.send_packet(0x49, &data).unwrap(), 0x4 => self.send_packet(0x49, &data).unwrap(),
0x4a => (), 0x4a => (),
0x9 => self.0.stream.lock().unwrap().handle(cmd, data), 0x9 | 0xa => 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 = String::from_utf8(data).unwrap(); self.0.data.write().unwrap().country = String::from_utf8(data).unwrap();
@ -275,10 +275,14 @@ impl Session {
self.0.audio_file.lock().unwrap().request(self, file) self.0.audio_file.lock().unwrap().request(self, file)
} }
pub fn stream(&self, file: FileId, offset: u32, size: u32) -> mpsc::Receiver<StreamEvent> { pub fn stream(&self, file: FileId, offset: u32, size: u32) -> eventual::Stream<StreamEvent, StreamError> {
self.0.stream.lock().unwrap().request(self, file, offset, size) self.0.stream.lock().unwrap().request(self, file, offset, size)
} }
pub fn allocate_stream(&self) -> (ChannelId, eventual::Stream<StreamEvent, StreamError>) {
self.0.stream.lock().unwrap().allocate_stream()
}
pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> { pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> {
self.0.metadata.lock().unwrap().get(self, id) self.0.metadata.lock().unwrap().get(self, id)
} }

View file

@ -1,7 +1,8 @@
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::io::{Cursor, Seek, SeekFrom, Write}; use std::io::{Cursor, Seek, SeekFrom, Write};
use std::sync::mpsc; use eventual::{self, Async};
use util::{ArcVec, FileId}; use util::{ArcVec, FileId};
use connection::PacketHandler; use connection::PacketHandler;
@ -13,7 +14,10 @@ pub enum StreamEvent {
Data(ArcVec<u8>), Data(ArcVec<u8>),
} }
type ChannelId = u16; #[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
pub struct StreamError;
pub type ChannelId = u16;
enum ChannelMode { enum ChannelMode {
Header, Header,
@ -22,7 +26,7 @@ enum ChannelMode {
struct Channel { struct Channel {
mode: ChannelMode, mode: ChannelMode,
callback: mpsc::Sender<StreamEvent>, callback: Option<eventual::Sender<StreamEvent, StreamError>>,
} }
pub struct StreamManager { pub struct StreamManager {
@ -38,17 +42,29 @@ impl StreamManager {
} }
} }
pub fn allocate_stream(&mut self) -> (ChannelId, eventual::Stream<StreamEvent, StreamError>) {
let (tx, rx) = eventual::Stream::pair();
let channel_id = self.next_id;
self.next_id += 1;
self.channels.insert(channel_id,
Channel {
mode: ChannelMode::Header,
callback: Some(tx),
});
(channel_id, rx)
}
pub fn request(&mut self, pub fn request(&mut self,
session: &Session, session: &Session,
file: FileId, file: FileId,
offset: u32, offset: u32,
size: u32) size: u32)
-> mpsc::Receiver<StreamEvent> { -> eventual::Stream<StreamEvent, StreamError> {
let (tx, rx) = mpsc::channel(); let (channel_id, rx) = self.allocate_stream();
let channel_id = self.next_id;
self.next_id += 1;
let mut data: Vec<u8> = Vec::new(); let mut data: Vec<u8> = Vec::new();
data.write_u16::<BigEndian>(channel_id).unwrap(); data.write_u16::<BigEndian>(channel_id).unwrap();
@ -64,76 +80,69 @@ impl StreamManager {
session.send_packet(0x8, &data).unwrap(); session.send_packet(0x8, &data).unwrap();
self.channels.insert(channel_id,
Channel {
mode: ChannelMode::Header,
callback: tx,
});
rx rx
} }
} }
impl PacketHandler for StreamManager { impl Channel {
fn handle(&mut self, _cmd: u8, data: Vec<u8>) { fn handle_packet(&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]);
packet.read_u16::<BigEndian>().unwrap(); // Skip channel id
let id: ChannelId = packet.read_u16::<BigEndian>().unwrap(); if cmd == 0xa {
let mut close = false; self.callback.take().map(|c| c.fail(StreamError));
{ } else {
let channel = match self.channels.get_mut(&id) { match self.mode {
Some(ch) => ch,
None => {
return;
}
};
match channel.mode {
ChannelMode::Header => { ChannelMode::Header => {
let mut length = 0; let mut length = 0;
while packet.position() < data.len() as u64 && !close { while packet.position() < data.len() as u64 {
length = packet.read_u16::<BigEndian>().unwrap(); length = packet.read_u16::<BigEndian>().unwrap();
if length > 0 { if length > 0 {
let header_id = packet.read_u8().unwrap(); let header_id = packet.read_u8().unwrap();
channel.callback let header_data = data.clone()
.send(StreamEvent::Header(
header_id,
data.clone()
.offset(packet.position() as usize) .offset(packet.position() as usize)
.limit(length as usize - 1) .limit(length as usize - 1);
))
.unwrap_or_else(|_| { let header = StreamEvent::Header(header_id, header_data);
close = true;
}); self.callback = self.callback.take().and_then(|c| c.send(header).await().ok());
packet.seek(SeekFrom::Current(length as i64 - 1)).unwrap(); packet.seek(SeekFrom::Current(length as i64 - 1)).unwrap();
} }
} }
if length == 0 { if length == 0 {
channel.mode = ChannelMode::Data; self.mode = ChannelMode::Data;
} }
} }
ChannelMode::Data => { ChannelMode::Data => {
if packet.position() < data.len() as u64 { if packet.position() < data.len() as u64 {
channel.callback let event_data = data.clone().offset(packet.position() as usize);
.send(StreamEvent::Data(data.clone() let event = StreamEvent::Data(event_data);
.offset(packet.position() as usize)))
.unwrap_or_else(|_| {
close = true;
});
} else {
close = true;
}
}
}
}
if close { self.callback = self.callback.take().and_then(|c| c.send(event).await().ok());
self.channels.remove(&id); } else {
self.callback = None;
}
}
}
}
}
}
impl PacketHandler for StreamManager {
fn handle(&mut self, cmd: u8, data: Vec<u8>) {
let id: ChannelId = BigEndian::read_u16(&data[0..2]);
if let Entry::Occupied(mut entry) = self.channels.entry(id) {
entry.get_mut().handle_packet(cmd, data);
if entry.get().callback.is_none() {
entry.remove();
}
} }
} }
} }