Merge pull request #236 from haroldmrt/master

Add option to specify preferred AP port
This commit is contained in:
Sasha Hilton 2018-08-06 20:44:27 +01:00 committed by GitHub
commit ee87904ac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 53 additions and 26 deletions

View file

@ -17,7 +17,11 @@ pub struct APResolveData {
ap_list: Vec<String>, ap_list: Vec<String>,
} }
fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String, Error = Error>> { fn apresolve(
handle: &Handle,
proxy: &Option<Url>,
ap_port: &Option<u16>,
) -> Box<Future<Item = String, Error = Error>> {
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL"); let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
let use_proxy = proxy.is_some(); let use_proxy = proxy.is_some();
@ -53,9 +57,15 @@ fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String,
let data = let data =
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON")); body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
let p = ap_port.clone();
let ap = data.and_then(move |data| { let ap = data.and_then(move |data| {
let mut aps = data.ap_list.iter().filter(|ap| { let mut aps = data.ap_list.iter().filter(|ap| {
if use_proxy { if p.is_some() {
Uri::from_str(ap)
.ok()
.map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap()))
} else if use_proxy {
// It is unlikely that the proxy will accept CONNECT on anything other than 443. // It is unlikely that the proxy will accept CONNECT on anything other than 443.
Uri::from_str(ap) Uri::from_str(ap)
.ok() .ok()
@ -75,11 +85,12 @@ fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String,
pub(crate) fn apresolve_or_fallback<E>( pub(crate) fn apresolve_or_fallback<E>(
handle: &Handle, handle: &Handle,
proxy: &Option<Url>, proxy: &Option<Url>,
ap_port: &Option<u16>,
) -> Box<Future<Item = String, Error = E>> ) -> Box<Future<Item = String, Error = E>>
where where
E: 'static, E: 'static,
{ {
let ap = apresolve(handle, proxy).or_else(|e| { let ap = apresolve(handle, proxy, ap_port).or_else(|e| {
warn!("Failed to resolve Access Point: {}", e.description()); warn!("Failed to resolve Access Point: {}", e.description());
warn!("Using fallback \"{}\"", AP_FALLBACK); warn!("Using fallback \"{}\"", AP_FALLBACK);
Ok(AP_FALLBACK.into()) Ok(AP_FALLBACK.into())

View file

@ -10,6 +10,7 @@ pub struct SessionConfig {
pub user_agent: String, pub user_agent: String,
pub device_id: String, pub device_id: String,
pub proxy: Option<Url>, pub proxy: Option<Url>,
pub ap_port: Option<u16>,
} }
impl Default for SessionConfig { impl Default for SessionConfig {
@ -19,6 +20,7 @@ impl Default for SessionConfig {
user_agent: version::version_string(), user_agent: version::version_string(),
device_id: device_id, device_id: device_id,
proxy: None, proxy: None,
ap_port: None,
} }
} }
} }

View file

@ -51,7 +51,7 @@ impl Session {
cache: Option<Cache>, cache: Option<Cache>,
handle: Handle, handle: Handle,
) -> Box<Future<Item = Session, Error = io::Error>> { ) -> Box<Future<Item = Session, Error = io::Error>> {
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy); let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
let handle_ = handle.clone(); let handle_ = handle.clone();
let proxy = config.proxy.clone(); let proxy = config.proxy.clone();

View file

@ -6,7 +6,7 @@ An AP is randomly picked from that list to connect to.
The connection is done using a bare TCP socket. Despite many APs using ports 80 and 443, neither HTTP nor TLS are used to connect. The connection is done using a bare TCP socket. Despite many APs using ports 80 and 443, neither HTTP nor TLS are used to connect.
If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:80` is used as a fallback. If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:443` is used as a fallback.
## Connection Hello ## Connection Hello
The first 3 packets exchanged are unencrypted, and have the following format : The first 3 packets exchanged are unencrypted, and have the following format :

View file

@ -33,7 +33,8 @@ fn main() {
let backend = audio_backend::find(None).unwrap(); let backend = audio_backend::find(None).unwrap();
println!("Connecting .."); println!("Connecting ..");
let session = core.run(Session::connect(session_config, credentials, None, handle)) let session = core
.run(Session::connect(session_config, credentials, None, handle))
.unwrap(); .unwrap();
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None)); let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None));

View file

@ -110,13 +110,15 @@ impl Metadata for Track {
fn parse(msg: &Self::Message, session: &Session) -> Self { fn parse(msg: &Self::Message, session: &Session) -> Self {
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()).unwrap()) .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
.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| {
@ -133,7 +135,8 @@ impl Metadata for Track {
album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(), album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
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()).unwrap()) .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
.collect(), .collect(),
@ -150,20 +153,23 @@ 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()).unwrap()) .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
.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()).unwrap()) .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
.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())
@ -194,7 +200,8 @@ impl Metadata for Artist {
fn parse(msg: &Self::Message, session: &Session) -> Self { fn parse(msg: &Self::Message, session: &Session) -> Self {
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))
{ {

View file

@ -557,7 +557,8 @@ impl PlayerInternal {
} }
}; };
let key = self.session let key = self
.session
.audio_key() .audio_key()
.request(track.id, file_id) .request(track.id, file_id)
.wait() .wait()
@ -599,7 +600,8 @@ impl Drop for PlayerInternal {
impl ::std::fmt::Debug for PlayerCommand { impl ::std::fmt::Debug for PlayerCommand {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self { match *self {
PlayerCommand::Load(track, play, position, _) => f.debug_tuple("Load") PlayerCommand::Load(track, play, position, _) => f
.debug_tuple("Load")
.field(&track) .field(&track)
.field(&play) .field(&play)
.field(&position) .field(&position)

View file

@ -1,6 +1,6 @@
// Autogenerated by build.sh // Autogenerated by build.sh
pub const FILES : &'static [(&'static str, u32)] = &[ pub const FILES: &'static [(&'static str, u32)] = &[
("proto/authentication.proto", 2098196376), ("proto/authentication.proto", 2098196376),
("proto/keyexchange.proto", 451735664), ("proto/keyexchange.proto", 451735664),
("proto/mercury.proto", 709993906), ("proto/mercury.proto", 709993906),

View file

@ -129,6 +129,7 @@ fn setup(args: &[String]) -> Setup {
.optopt("u", "username", "Username to sign in with", "USERNAME") .optopt("u", "username", "Username to sign in with", "USERNAME")
.optopt("p", "password", "Password", "PASSWORD") .optopt("p", "password", "Password", "PASSWORD")
.optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY") .optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
.optopt("", "ap-port", "Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070", "AP_PORT")
.optflag("", "disable-discovery", "Disable discovery mode") .optflag("", "disable-discovery", "Disable discovery mode")
.optopt( .optopt(
"", "",
@ -255,20 +256,23 @@ fn setup(args: &[String]) -> Setup {
proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map( proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map(
|s| { |s| {
match Url::parse(&s) { match Url::parse(&s) {
Ok(url) => { Ok(url) => {
if url.host().is_none() || url.port().is_none() { if url.host().is_none() || url.port().is_none() {
panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed"); panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
} }
if url.scheme() != "http" { if url.scheme() != "http" {
panic!("Only unsecure http:// proxies are supported"); panic!("Only unsecure http:// proxies are supported");
}
url
},
Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
} }
url
},
Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
}
}, },
), ),
ap_port: matches
.opt_str("ap-port")
.map(|port| port.parse::<u16>().expect("Invalid port")),
} }
}; };