use protobuf::{self, Message}; use std::any::{Any, TypeId}; use std::collections::HashMap; use std::fmt; use std::slice::bytes::copy_memory; use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak}; use std::thread; use librespot_protocol as protocol; use mercury::{MercuryRequest, MercuryMethod}; use util::{SpotifyId, FileId}; use session::Session; pub trait MetadataTrait : Send + Any + 'static { type Message: protobuf::MessageStatic; fn from_msg(msg: &Self::Message) -> Self; fn base_url() -> &'static str; fn request(r: MetadataRef) -> MetadataRequest; } #[derive(Debug)] pub struct Track { pub name: String, pub album: SpotifyId, pub files: Vec } impl MetadataTrait for Track { type Message = protocol::metadata::Track; fn from_msg(msg: &Self::Message) -> Self { Track { name: msg.get_name().to_string(), album: SpotifyId::from_raw(msg.get_album().get_gid()), files: msg.get_file().iter() .map(|file| { let mut dst = [0u8; 20]; copy_memory(&file.get_file_id(), &mut dst); FileId(dst) }) .collect(), } } fn base_url() -> &'static str { "hm://metadata/3/track" } fn request(r: MetadataRef) -> MetadataRequest { MetadataRequest::Track(r) } } #[derive(Debug)] pub struct Album { pub name: String, pub artists: Vec, pub covers: Vec } impl MetadataTrait for Album { type Message = protocol::metadata::Album; fn from_msg(msg: &Self::Message) -> Self { Album { name: msg.get_name().to_string(), artists: msg.get_artist().iter() .map(|a| SpotifyId::from_raw(a.get_gid())) .collect(), covers: msg.get_cover_group().get_image().iter() .map(|image| { let mut dst = [0u8; 20]; copy_memory(&image.get_file_id(), &mut dst); FileId(dst) }) .collect(), } } fn base_url() -> &'static str { "hm://metadata/3/album" } fn request(r: MetadataRef) -> MetadataRequest { MetadataRequest::Album(r) } } #[derive(Debug)] pub struct Artist { pub name: String, } impl MetadataTrait for Artist { type Message = protocol::metadata::Artist; fn from_msg(msg: &Self::Message) -> Self { Artist { name: msg.get_name().to_string(), } } fn base_url() -> &'static str { "hm://metadata/3/artist" } fn request(r: MetadataRef) -> MetadataRequest { MetadataRequest::Artist(r) } } #[derive(Debug)] pub enum MetadataState { Loading, Loaded(T), Error, } pub struct Metadata { id: SpotifyId, state: Mutex>, cond: Condvar } pub type MetadataRef = Arc>; pub type TrackRef = MetadataRef; pub type AlbumRef = MetadataRef; pub type ArtistRef = MetadataRef; impl Metadata { pub fn id(&self) -> SpotifyId { self.id } pub fn lock(&self) -> MutexGuard> { self.state.lock().unwrap() } pub fn wait(&self) -> MutexGuard> { let mut handle = self.lock(); while handle.is_loading() { handle = self.cond.wait(handle).unwrap(); } handle } pub fn set(&self, state: MetadataState) { let mut handle = self.lock(); *handle = state; self.cond.notify_all(); } } impl fmt::Debug for Metadata { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "Metadata<>({:?}, {:?})", self.id, *self.lock()) } } impl MetadataState { pub fn is_loading(&self) -> bool { match *self { MetadataState::Loading => true, _ => false } } pub fn is_loaded(&self) -> bool { match *self { MetadataState::Loaded(_) => true, _ => false } } pub fn unwrap<'s>(&'s self) -> &'s T { match *self { MetadataState::Loaded(ref data) => data, _ => panic!("Not loaded") } } } #[derive(Debug)] pub enum MetadataRequest { Artist(ArtistRef), Album(AlbumRef), Track(TrackRef) } pub struct MetadataManager { cache: HashMap<(SpotifyId, TypeId), Box> } impl MetadataManager { pub fn new() -> MetadataManager { MetadataManager { cache: HashMap::new() } } pub fn get(&mut self, session: &Session, id: SpotifyId) -> MetadataRef { let key = (id, TypeId::of::()); self.cache.get(&key) .and_then(|x| x.downcast_ref::>>()) .and_then(|x| x.upgrade()) .unwrap_or_else(|| { let x : MetadataRef = 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 (&self, session: &Session, object: MetadataRef) { let rx = session.mercury(MercuryRequest { method: MercuryMethod::GET, uri: format!("{}/{}", T::base_url(), object.id.to_base16()), content_type: None, payload: Vec::new() }); thread::spawn(move || { let response = rx.into_inner(); let msg : T::Message = protobuf::parse_from_bytes( response.payload.front().unwrap()).unwrap(); object.set(MetadataState::Loaded(T::from_msg(&msg))); }); } }