metadata.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. use eventual::{Async, Future};
  2. use protobuf;
  3. use protocol;
  4. use mercury::{MercuryRequest, MercuryMethod};
  5. use util::{SpotifyId, FileId, StrChunksExt};
  6. use session::Session;
  7. pub use protocol::metadata::AudioFile_Format as FileFormat;
  8. fn countrylist_contains(list: &str, country: &str) -> bool {
  9. list.chunks(2).any(|cc| cc == country)
  10. }
  11. fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
  12. where I: IntoIterator<Item = &'s protocol::metadata::Restriction>
  13. {
  14. restrictions.into_iter()
  15. .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()))
  16. .all(|r| {
  17. !countrylist_contains(r.get_countries_forbidden(), country) &&
  18. (!r.has_countries_allowed() ||
  19. countrylist_contains(r.get_countries_allowed(), country))
  20. })
  21. }
  22. pub trait MetadataTrait : Send + 'static {
  23. type Message: protobuf::MessageStatic;
  24. fn base_url() -> &'static str;
  25. fn parse(msg: &Self::Message, session: &Session) -> Self;
  26. }
  27. #[derive(Debug)]
  28. pub struct Track {
  29. pub id: SpotifyId,
  30. pub name: String,
  31. pub album: SpotifyId,
  32. pub artists: Vec<SpotifyId>,
  33. pub files: Vec<(FileId, FileFormat)>,
  34. pub alternatives: Vec<SpotifyId>,
  35. pub available: bool,
  36. }
  37. #[derive(Debug)]
  38. pub struct Album {
  39. pub id: SpotifyId,
  40. pub name: String,
  41. pub artists: Vec<SpotifyId>,
  42. pub tracks: Vec<SpotifyId>,
  43. pub covers: Vec<FileId>,
  44. }
  45. #[derive(Debug)]
  46. pub struct Artist {
  47. pub id: SpotifyId,
  48. pub name: String,
  49. pub top_tracks: Vec<SpotifyId>,
  50. }
  51. pub type MetadataRef<T> = Future<T, ()>;
  52. pub type TrackRef = MetadataRef<Track>;
  53. pub type AlbumRef = MetadataRef<Album>;
  54. pub type ArtistRef = MetadataRef<Artist>;
  55. impl MetadataTrait for Track {
  56. type Message = protocol::metadata::Track;
  57. fn base_url() -> &'static str {
  58. "hm://metadata/3/track"
  59. }
  60. fn parse(msg: &Self::Message, session: &Session) -> Self {
  61. let country = session.country();
  62. let artists = msg.get_artist()
  63. .iter()
  64. .filter(|artist| artist.has_gid())
  65. .map(|artist| SpotifyId::from_raw(artist.get_gid()))
  66. .collect::<Vec<_>>();
  67. let files = msg.get_file()
  68. .iter()
  69. .filter(|file| file.has_file_id())
  70. .map(|file| {
  71. let mut dst = [0u8; 20];
  72. dst.clone_from_slice(&file.get_file_id());
  73. (FileId(dst), file.get_format())
  74. })
  75. .collect();
  76. Track {
  77. id: SpotifyId::from_raw(msg.get_gid()),
  78. name: msg.get_name().to_owned(),
  79. album: SpotifyId::from_raw(msg.get_album().get_gid()),
  80. artists: artists,
  81. files: files,
  82. alternatives: msg.get_alternative()
  83. .iter()
  84. .map(|alt| SpotifyId::from_raw(alt.get_gid()))
  85. .collect(),
  86. available: parse_restrictions(msg.get_restriction(),
  87. &country,
  88. "premium"),
  89. }
  90. }
  91. }
  92. impl MetadataTrait for Album {
  93. type Message = protocol::metadata::Album;
  94. fn base_url() -> &'static str {
  95. "hm://metadata/3/album"
  96. }
  97. fn parse(msg: &Self::Message, _: &Session) -> Self {
  98. let artists = msg.get_artist()
  99. .iter()
  100. .filter(|artist| artist.has_gid())
  101. .map(|artist| SpotifyId::from_raw(artist.get_gid()))
  102. .collect::<Vec<_>>();
  103. let tracks = msg.get_disc()
  104. .iter()
  105. .flat_map(|disc| disc.get_track())
  106. .filter(|track| track.has_gid())
  107. .map(|track| SpotifyId::from_raw(track.get_gid()))
  108. .collect::<Vec<_>>();
  109. let covers = msg.get_cover_group()
  110. .get_image()
  111. .iter()
  112. .filter(|image| image.has_file_id())
  113. .map(|image| {
  114. let mut dst = [0u8; 20];
  115. dst.clone_from_slice(&image.get_file_id());
  116. FileId(dst)
  117. })
  118. .collect::<Vec<_>>();
  119. Album {
  120. id: SpotifyId::from_raw(msg.get_gid()),
  121. name: msg.get_name().to_owned(),
  122. artists: artists,
  123. tracks: tracks,
  124. covers: covers,
  125. }
  126. }
  127. }
  128. impl MetadataTrait for Artist {
  129. type Message = protocol::metadata::Artist;
  130. fn base_url() -> &'static str {
  131. "hm://metadata/3/artist"
  132. }
  133. fn parse(msg: &Self::Message, session: &Session) -> Self {
  134. let country = session.country();
  135. let top_tracks = msg.get_top_track()
  136. .iter()
  137. .filter(|tt| !tt.has_country() ||
  138. countrylist_contains(tt.get_country(), &country))
  139. .next()
  140. .unwrap()
  141. .get_track()
  142. .iter()
  143. .filter(|track| track.has_gid())
  144. .map(|track| SpotifyId::from_raw(track.get_gid()))
  145. .collect::<Vec<_>>();
  146. Artist {
  147. id: SpotifyId::from_raw(msg.get_gid()),
  148. name: msg.get_name().to_owned(),
  149. top_tracks: top_tracks
  150. }
  151. }
  152. }
  153. pub struct MetadataManager;
  154. impl MetadataManager {
  155. pub fn new() -> MetadataManager {
  156. MetadataManager
  157. }
  158. pub fn get<T: MetadataTrait>(&mut self, session: &Session, id: SpotifyId) -> MetadataRef<T> {
  159. let session = session.clone();
  160. session.mercury(MercuryRequest {
  161. method: MercuryMethod::GET,
  162. uri: format!("{}/{}", T::base_url(), id.to_base16()),
  163. content_type: None,
  164. payload: Vec::new(),
  165. })
  166. .and_then(move |response| {
  167. let data = response.payload.first().unwrap();
  168. let msg: T::Message = protobuf::parse_from_bytes(data).unwrap();
  169. Ok(T::parse(&msg, &session))
  170. })
  171. }
  172. }