lib.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. extern crate byteorder;
  2. extern crate futures;
  3. extern crate linear_map;
  4. extern crate protobuf;
  5. extern crate librespot_core as core;
  6. extern crate librespot_protocol as protocol;
  7. pub mod cover;
  8. use futures::Future;
  9. use linear_map::LinearMap;
  10. use core::mercury::MercuryError;
  11. use core::session::Session;
  12. use core::spotify_id::{FileId, SpotifyId};
  13. pub use protocol::metadata::AudioFile_Format as FileFormat;
  14. fn countrylist_contains(list: &str, country: &str) -> bool {
  15. list.chunks(2).any(|cc| cc == country)
  16. }
  17. fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
  18. where
  19. I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
  20. {
  21. let mut forbidden = "".to_string();
  22. let mut has_forbidden = false;
  23. let mut allowed = "".to_string();
  24. let mut has_allowed = false;
  25. let rs = restrictions
  26. .into_iter()
  27. .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));
  28. for r in rs {
  29. if r.has_countries_forbidden() {
  30. forbidden.push_str(r.get_countries_forbidden());
  31. has_forbidden = true;
  32. }
  33. if r.has_countries_allowed() {
  34. allowed.push_str(r.get_countries_allowed());
  35. has_allowed = true;
  36. }
  37. }
  38. (has_forbidden || has_allowed)
  39. && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
  40. && (!has_allowed || countrylist_contains(allowed.as_str(), country))
  41. }
  42. pub trait Metadata: Send + Sized + 'static {
  43. type Message: protobuf::MessageStatic;
  44. fn base_url() -> &'static str;
  45. fn parse(msg: &Self::Message, session: &Session) -> Self;
  46. fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
  47. let uri = format!("{}/{}", Self::base_url(), id.to_base16());
  48. let request = session.mercury().get(uri);
  49. let session = session.clone();
  50. Box::new(request.and_then(move |response| {
  51. let data = response.payload.first().expect("Empty payload");
  52. let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
  53. Ok(Self::parse(&msg, &session))
  54. }))
  55. }
  56. }
  57. #[derive(Debug, Clone)]
  58. pub struct Track {
  59. pub id: SpotifyId,
  60. pub name: String,
  61. pub duration: i32,
  62. pub album: SpotifyId,
  63. pub artists: Vec<SpotifyId>,
  64. pub files: LinearMap<FileFormat, FileId>,
  65. pub alternatives: Vec<SpotifyId>,
  66. pub available: bool,
  67. }
  68. #[derive(Debug, Clone)]
  69. pub struct Album {
  70. pub id: SpotifyId,
  71. pub name: String,
  72. pub artists: Vec<SpotifyId>,
  73. pub tracks: Vec<SpotifyId>,
  74. pub covers: Vec<FileId>,
  75. }
  76. #[derive(Debug, Clone)]
  77. pub struct Artist {
  78. pub id: SpotifyId,
  79. pub name: String,
  80. pub top_tracks: Vec<SpotifyId>,
  81. }
  82. impl Metadata for Track {
  83. type Message = protocol::metadata::Track;
  84. fn base_url() -> &'static str {
  85. "hm://metadata/3/track"
  86. }
  87. fn parse(msg: &Self::Message, session: &Session) -> Self {
  88. let country = session.country();
  89. let artists = msg.get_artist()
  90. .iter()
  91. .filter(|artist| artist.has_gid())
  92. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  93. .collect::<Vec<_>>();
  94. let files = msg.get_file()
  95. .iter()
  96. .filter(|file| file.has_file_id())
  97. .map(|file| {
  98. let mut dst = [0u8; 20];
  99. dst.clone_from_slice(file.get_file_id());
  100. (file.get_format(), FileId(dst))
  101. })
  102. .collect();
  103. Track {
  104. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  105. name: msg.get_name().to_owned(),
  106. duration: msg.get_duration(),
  107. album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
  108. artists: artists,
  109. files: files,
  110. alternatives: msg.get_alternative()
  111. .iter()
  112. .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
  113. .collect(),
  114. available: parse_restrictions(msg.get_restriction(), &country, "premium"),
  115. }
  116. }
  117. }
  118. impl Metadata for Album {
  119. type Message = protocol::metadata::Album;
  120. fn base_url() -> &'static str {
  121. "hm://metadata/3/album"
  122. }
  123. fn parse(msg: &Self::Message, _: &Session) -> Self {
  124. let artists = msg.get_artist()
  125. .iter()
  126. .filter(|artist| artist.has_gid())
  127. .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
  128. .collect::<Vec<_>>();
  129. let tracks = msg.get_disc()
  130. .iter()
  131. .flat_map(|disc| disc.get_track())
  132. .filter(|track| track.has_gid())
  133. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  134. .collect::<Vec<_>>();
  135. let covers = msg.get_cover_group()
  136. .get_image()
  137. .iter()
  138. .filter(|image| image.has_file_id())
  139. .map(|image| {
  140. let mut dst = [0u8; 20];
  141. dst.clone_from_slice(image.get_file_id());
  142. FileId(dst)
  143. })
  144. .collect::<Vec<_>>();
  145. Album {
  146. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  147. name: msg.get_name().to_owned(),
  148. artists: artists,
  149. tracks: tracks,
  150. covers: covers,
  151. }
  152. }
  153. }
  154. impl Metadata for Artist {
  155. type Message = protocol::metadata::Artist;
  156. fn base_url() -> &'static str {
  157. "hm://metadata/3/artist"
  158. }
  159. fn parse(msg: &Self::Message, session: &Session) -> Self {
  160. let country = session.country();
  161. let top_tracks: Vec<SpotifyId> = match msg.get_top_track()
  162. .iter()
  163. .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
  164. {
  165. Some(tracks) => tracks
  166. .get_track()
  167. .iter()
  168. .filter(|track| track.has_gid())
  169. .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
  170. .collect::<Vec<_>>(),
  171. None => Vec::new(),
  172. };
  173. Artist {
  174. id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
  175. name: msg.get_name().to_owned(),
  176. top_tracks: top_tracks,
  177. }
  178. }
  179. }
  180. struct StrChunks<'s>(&'s str, usize);
  181. trait StrChunksExt {
  182. fn chunks(&self, size: usize) -> StrChunks;
  183. }
  184. impl StrChunksExt for str {
  185. fn chunks(&self, size: usize) -> StrChunks {
  186. StrChunks(self, size)
  187. }
  188. }
  189. impl<'s> Iterator for StrChunks<'s> {
  190. type Item = &'s str;
  191. fn next(&mut self) -> Option<&'s str> {
  192. let &mut StrChunks(data, size) = self;
  193. if data.is_empty() {
  194. None
  195. } else {
  196. let ret = Some(&data[..size]);
  197. self.0 = &data[size..];
  198. ret
  199. }
  200. }
  201. }