mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
commit
1de8049a58
15 changed files with 101 additions and 104 deletions
|
@ -11,7 +11,7 @@ use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use core::channel::{Channel, ChannelData, ChannelError, ChannelHeaders};
|
use core::channel::{Channel, ChannelData, ChannelError, ChannelHeaders};
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::FileId;
|
use core::spotify_id::FileId;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 0x20000;
|
const CHUNK_SIZE: usize = 0x20000;
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ impl Future for AudioFileOpenStreaming {
|
||||||
if id == 0x3 {
|
if id == 0x3 {
|
||||||
let size = BigEndian::read_u32(&data) as usize * 4;
|
let size = BigEndian::read_u32(&data) as usize * 4;
|
||||||
let file = self.finish(size);
|
let file = self.finish(size);
|
||||||
|
|
||||||
return Ok(Async::Ready(file));
|
return Ok(Async::Ready(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ use protobuf::{self, Message};
|
||||||
use core::config::ConnectConfig;
|
use core::config::ConnectConfig;
|
||||||
use core::mercury::MercuryError;
|
use core::mercury::MercuryError;
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::{SeqGenerator, SpotifyId};
|
use core::spotify_id::SpotifyId;
|
||||||
|
use core::util::SeqGenerator;
|
||||||
use core::version;
|
use core::version;
|
||||||
|
|
||||||
use protocol;
|
use protocol;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use futures::sync::oneshot;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use util::{FileId, SpotifyId};
|
use spotify_id::{FileId, SpotifyId};
|
||||||
use util::SeqGenerator;
|
use util::SeqGenerator;
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use authentication::Credentials;
|
use authentication::Credentials;
|
||||||
use util::FileId;
|
use spotify_id::FileId;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
|
|
|
@ -4,7 +4,7 @@ macro_rules! component {
|
||||||
pub struct $name(::std::sync::Arc<($crate::session::SessionWeak, ::std::sync::Mutex<$inner>)>);
|
pub struct $name(::std::sync::Arc<($crate::session::SessionWeak, ::std::sync::Mutex<$inner>)>);
|
||||||
impl $name {
|
impl $name {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn new(session: $crate::session::SessionWeak) -> $name {
|
pub(crate) fn new(session: $crate::session::SessionWeak) -> $name {
|
||||||
debug!(target:"librespot::component", "new {}", stringify!($name));
|
debug!(target:"librespot::component", "new {}", stringify!($name));
|
||||||
|
|
||||||
$name(::std::sync::Arc::new((session, ::std::sync::Mutex::new($inner {
|
$name(::std::sync::Arc::new((session, ::std::sync::Mutex::new($inner {
|
||||||
|
@ -39,17 +39,17 @@ macro_rules! component {
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
pub struct Lazy<T>(Mutex<bool>, UnsafeCell<Option<T>>);
|
pub(crate) struct Lazy<T>(Mutex<bool>, UnsafeCell<Option<T>>);
|
||||||
unsafe impl<T: Sync> Sync for Lazy<T> {}
|
unsafe impl<T: Sync> Sync for Lazy<T> {}
|
||||||
unsafe impl<T: Send> Send for Lazy<T> {}
|
unsafe impl<T: Send> Send for Lazy<T> {}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))]
|
#[cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))]
|
||||||
impl<T> Lazy<T> {
|
impl<T> Lazy<T> {
|
||||||
pub fn new() -> Lazy<T> {
|
pub(crate) fn new() -> Lazy<T> {
|
||||||
Lazy(Mutex::new(false), UnsafeCell::new(None))
|
Lazy(Mutex::new(false), UnsafeCell::new(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<F: FnOnce() -> T>(&self, f: F) -> &T {
|
pub(crate) fn get<F: FnOnce() -> T>(&self, f: F) -> &T {
|
||||||
let mut inner = self.0.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
if !*inner {
|
if !*inner {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -44,5 +44,6 @@ pub mod diffie_hellman;
|
||||||
pub mod keymaster;
|
pub mod keymaster;
|
||||||
pub mod mercury;
|
pub mod mercury;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod spotify_id;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl MercuryManager {
|
||||||
seq
|
seq
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(&self, req: MercuryRequest) -> MercuryFuture<MercuryResponse> {
|
fn request(&self, req: MercuryRequest) -> MercuryFuture<MercuryResponse> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
let pending = MercuryPending {
|
let pending = MercuryPending {
|
||||||
|
|
|
@ -19,12 +19,12 @@ use audio_key::AudioKeyManager;
|
||||||
use channel::ChannelManager;
|
use channel::ChannelManager;
|
||||||
use mercury::MercuryManager;
|
use mercury::MercuryManager;
|
||||||
|
|
||||||
pub struct SessionData {
|
struct SessionData {
|
||||||
country: String,
|
country: String,
|
||||||
canonical_username: String,
|
canonical_username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SessionInternal {
|
struct SessionInternal {
|
||||||
config: SessionConfig,
|
config: SessionConfig,
|
||||||
data: RwLock<SessionData>,
|
data: RwLock<SessionData>,
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ pub struct SessionInternal {
|
||||||
static SESSION_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
static SESSION_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Session(pub Arc<SessionInternal>);
|
pub struct Session(Arc<SessionInternal>);
|
||||||
|
|
||||||
pub fn device_id(name: &str) -> String {
|
pub fn device_id(name: &str) -> String {
|
||||||
let mut h = Sha1::new();
|
let mut h = Sha1::new();
|
||||||
|
@ -226,7 +226,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SessionWeak(pub Weak<SessionInternal>);
|
pub struct SessionWeak(Weak<SessionInternal>);
|
||||||
|
|
||||||
impl SessionWeak {
|
impl SessionWeak {
|
||||||
fn try_upgrade(&self) -> Option<Session> {
|
fn try_upgrade(&self) -> Option<Session> {
|
||||||
|
|
|
@ -6,10 +6,8 @@ use std::mem;
|
||||||
use std::ops::{Mul, Rem, Shr};
|
use std::ops::{Mul, Rem, Shr};
|
||||||
|
|
||||||
mod int128;
|
mod int128;
|
||||||
mod spotify_id;
|
|
||||||
|
|
||||||
pub use util::int128::u128;
|
pub use util::int128::u128;
|
||||||
pub use util::spotify_id::{FileId, SpotifyId};
|
|
||||||
|
|
||||||
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
||||||
rng.gen_iter().take(size).collect()
|
rng.gen_iter().take(size).collect()
|
||||||
|
@ -31,32 +29,6 @@ pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StrChunks<'s>(&'s str, usize);
|
|
||||||
|
|
||||||
pub trait StrChunksExt {
|
|
||||||
fn chunks(&self, size: usize) -> StrChunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StrChunksExt for str {
|
|
||||||
fn chunks(&self, size: usize) -> StrChunks {
|
|
||||||
StrChunks(self, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Iterator for StrChunks<'s> {
|
|
||||||
type Item = &'s str;
|
|
||||||
fn next(&mut self) -> Option<&'s str> {
|
|
||||||
let &mut StrChunks(data, size) = self;
|
|
||||||
if data.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let ret = Some(&data[..size]);
|
|
||||||
self.0 = &data[size..];
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ReadSeek: ::std::io::Read + ::std::io::Seek {}
|
pub trait ReadSeek: ::std::io::Read + ::std::io::Seek {}
|
||||||
impl<T: ::std::io::Read + ::std::io::Seek> ReadSeek for T {}
|
impl<T: ::std::io::Read + ::std::io::Seek> ReadSeek for T {}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use tokio_core::reactor::Core;
|
||||||
use librespot::core::authentication::Credentials;
|
use librespot::core::authentication::Credentials;
|
||||||
use librespot::playback::config::{PlayerConfig, SessionConfig};
|
use librespot::playback::config::{PlayerConfig, SessionConfig};
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
use librespot::core::util::SpotifyId;
|
use librespot::core::spotify_id::SpotifyId;
|
||||||
|
|
||||||
use librespot::audio_backend;
|
use librespot::audio_backend;
|
||||||
use librespot::player::Player;
|
use librespot::player::Player;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::io::Write;
|
||||||
|
|
||||||
use core::channel::ChannelData;
|
use core::channel::ChannelData;
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::FileId;
|
use core::spotify_id::FileId;
|
||||||
|
|
||||||
pub fn get(session: &Session, file: FileId) -> ChannelData {
|
pub fn get(session: &Session, file: FileId) -> ChannelData {
|
||||||
let (channel_id, channel) = session.channel().allocate();
|
let (channel_id, channel) = session.channel().allocate();
|
||||||
|
|
|
@ -13,7 +13,7 @@ use linear_map::LinearMap;
|
||||||
|
|
||||||
use core::mercury::MercuryError;
|
use core::mercury::MercuryError;
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::{SpotifyId, FileId, StrChunksExt};
|
use core::spotify_id::{FileId, SpotifyId};
|
||||||
|
|
||||||
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
||||||
|
|
||||||
|
@ -22,7 +22,8 @@ fn countrylist_contains(list: &str, country: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
|
fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
|
||||||
where I: IntoIterator<Item = &'s protocol::metadata::Restriction>
|
where
|
||||||
|
I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
|
||||||
{
|
{
|
||||||
let mut forbidden = "".to_string();
|
let mut forbidden = "".to_string();
|
||||||
let mut has_forbidden = false;
|
let mut has_forbidden = false;
|
||||||
|
@ -30,9 +31,9 @@ fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) ->
|
||||||
let mut allowed = "".to_string();
|
let mut allowed = "".to_string();
|
||||||
let mut has_allowed = false;
|
let mut has_allowed = false;
|
||||||
|
|
||||||
let rs = restrictions.into_iter().filter(|r|
|
let rs = restrictions
|
||||||
r.get_catalogue_str().contains(&catalogue.to_owned())
|
.into_iter()
|
||||||
);
|
.filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));
|
||||||
|
|
||||||
for r in rs {
|
for r in rs {
|
||||||
if r.has_countries_forbidden() {
|
if r.has_countries_forbidden() {
|
||||||
|
@ -46,12 +47,12 @@ fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(has_forbidden || has_allowed) &&
|
(has_forbidden || has_allowed)
|
||||||
(!has_forbidden || !countrylist_contains(forbidden.as_str(), country)) &&
|
&& (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
|
||||||
(!has_allowed || countrylist_contains(allowed.as_str(), country))
|
&& (!has_allowed || countrylist_contains(allowed.as_str(), country))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Metadata : Send + Sized + 'static {
|
pub trait Metadata: Send + Sized + 'static {
|
||||||
type Message: protobuf::MessageStatic;
|
type Message: protobuf::MessageStatic;
|
||||||
|
|
||||||
fn base_url() -> &'static str;
|
fn base_url() -> &'static str;
|
||||||
|
@ -110,20 +111,20 @@ impl Metadata for Track {
|
||||||
let country = session.country();
|
let country = session.country();
|
||||||
|
|
||||||
let artists = msg.get_artist()
|
let artists = msg.get_artist()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|artist| artist.has_gid())
|
.filter(|artist| artist.has_gid())
|
||||||
.map(|artist| SpotifyId::from_raw(artist.get_gid()))
|
.map(|artist| SpotifyId::from_raw(artist.get_gid()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let files = msg.get_file()
|
let files = msg.get_file()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|file| file.has_file_id())
|
.filter(|file| file.has_file_id())
|
||||||
.map(|file| {
|
.map(|file| {
|
||||||
let mut dst = [0u8; 20];
|
let mut dst = [0u8; 20];
|
||||||
dst.clone_from_slice(file.get_file_id());
|
dst.clone_from_slice(file.get_file_id());
|
||||||
(file.get_format(), FileId(dst))
|
(file.get_format(), FileId(dst))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Track {
|
Track {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()),
|
id: SpotifyId::from_raw(msg.get_gid()),
|
||||||
|
@ -133,12 +134,10 @@ impl Metadata for Track {
|
||||||
artists: artists,
|
artists: artists,
|
||||||
files: files,
|
files: files,
|
||||||
alternatives: msg.get_alternative()
|
alternatives: msg.get_alternative()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|alt| SpotifyId::from_raw(alt.get_gid()))
|
.map(|alt| SpotifyId::from_raw(alt.get_gid()))
|
||||||
.collect(),
|
.collect(),
|
||||||
available: parse_restrictions(msg.get_restriction(),
|
available: parse_restrictions(msg.get_restriction(), &country, "premium"),
|
||||||
&country,
|
|
||||||
"premium"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,28 +151,28 @@ impl Metadata for Album {
|
||||||
|
|
||||||
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
||||||
let artists = msg.get_artist()
|
let artists = msg.get_artist()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|artist| artist.has_gid())
|
.filter(|artist| artist.has_gid())
|
||||||
.map(|artist| SpotifyId::from_raw(artist.get_gid()))
|
.map(|artist| SpotifyId::from_raw(artist.get_gid()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let tracks = msg.get_disc()
|
let tracks = msg.get_disc()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|disc| disc.get_track())
|
.flat_map(|disc| disc.get_track())
|
||||||
.filter(|track| track.has_gid())
|
.filter(|track| track.has_gid())
|
||||||
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let covers = msg.get_cover_group()
|
let covers = msg.get_cover_group()
|
||||||
.get_image()
|
.get_image()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|image| image.has_file_id())
|
.filter(|image| image.has_file_id())
|
||||||
.map(|image| {
|
.map(|image| {
|
||||||
let mut dst = [0u8; 20];
|
let mut dst = [0u8; 20];
|
||||||
dst.clone_from_slice(image.get_file_id());
|
dst.clone_from_slice(image.get_file_id());
|
||||||
FileId(dst)
|
FileId(dst)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Album {
|
Album {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()),
|
id: SpotifyId::from_raw(msg.get_gid()),
|
||||||
|
@ -185,7 +184,6 @@ impl Metadata for Album {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Metadata for Artist {
|
impl Metadata for Artist {
|
||||||
type Message = protocol::metadata::Artist;
|
type Message = protocol::metadata::Artist;
|
||||||
|
|
||||||
|
@ -197,23 +195,48 @@ impl Metadata for Artist {
|
||||||
let country = session.country();
|
let country = session.country();
|
||||||
|
|
||||||
let top_tracks: Vec<SpotifyId> = match msg.get_top_track()
|
let top_tracks: Vec<SpotifyId> = match msg.get_top_track()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country)) {
|
.find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
|
||||||
Some(tracks) => {
|
{
|
||||||
tracks.get_track()
|
Some(tracks) => tracks
|
||||||
.iter()
|
.get_track()
|
||||||
.filter(|track| track.has_gid())
|
.iter()
|
||||||
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
.filter(|track| track.has_gid())
|
||||||
.collect::<Vec<_>>()
|
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
||||||
},
|
.collect::<Vec<_>>(),
|
||||||
None => Vec::new()
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Artist {
|
Artist {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()),
|
id: SpotifyId::from_raw(msg.get_gid()),
|
||||||
name: msg.get_name().to_owned(),
|
name: msg.get_name().to_owned(),
|
||||||
top_tracks: top_tracks
|
top_tracks: top_tracks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StrChunks<'s>(&'s str, usize);
|
||||||
|
|
||||||
|
trait StrChunksExt {
|
||||||
|
fn chunks(&self, size: usize) -> StrChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StrChunksExt for str {
|
||||||
|
fn chunks(&self, size: usize) -> StrChunks {
|
||||||
|
StrChunks(self, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Iterator for StrChunks<'s> {
|
||||||
|
type Item = &'s str;
|
||||||
|
fn next(&mut self) -> Option<&'s str> {
|
||||||
|
let &mut StrChunks(data, size) = self;
|
||||||
|
if data.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let ret = Some(&data[..size]);
|
||||||
|
self.0 = &data[size..];
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use config::{Bitrate, PlayerConfig};
|
use config::{Bitrate, PlayerConfig};
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::SpotifyId;
|
use core::spotify_id::SpotifyId;
|
||||||
|
|
||||||
use audio_backend::Sink;
|
use audio_backend::Sink;
|
||||||
use audio::{AudioFile, AudioDecrypt};
|
use audio::{AudioFile, AudioDecrypt};
|
||||||
|
|
Loading…
Reference in a new issue