lib.rs 6.7 KB

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