Skip to content

Commit

Permalink
core/context: adjust context requests and update
Browse files Browse the repository at this point in the history
- search should now return the expected context
- removed workaround for single track playback
- move local playback check into update_context
- check track uri for invalid characters
- early return with `?`
  • Loading branch information
photovoltex committed Nov 26, 2024
1 parent 2a8d6e1 commit 81020fa
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 35 deletions.
7 changes: 4 additions & 3 deletions connect/src/spirc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,10 @@ impl SpircTask {

if context_uri.contains("spotify:show:") || context_uri.contains("spotify:episode:") {
// autoplay is not supported for podcasts
return Err(
SpircError::NotAllowedContext(ResolveContext::from_uri(context_uri, true)).into(),
);
Err(SpircError::NotAllowedContext(ResolveContext::from_uri(
context_uri,
true,
)))?
}

let previous_tracks = self.connect_state.prev_autoplay_track_uris();
Expand Down
5 changes: 4 additions & 1 deletion connect/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum StateError {
ContextHasNoTracks,
#[error("playback of local files is not supported")]
UnsupportedLocalPlayBack,
#[error("track uri <{0}> contains invalid characters")]
InvalidTrackUri(String),
}

impl From<StateError> for Error {
Expand All @@ -60,7 +62,8 @@ impl From<StateError> for Error {
| MessageFieldNone(_)
| NoContext(_)
| CanNotFindTrackInContext(_, _)
| ContextHasNoTracks => Error::failed_precondition(err),
| ContextHasNoTracks
| InvalidTrackUri(_) => Error::failed_precondition(err),
CurrentlyDisallowed { .. } | UnsupportedLocalPlayBack => Error::unavailable(err),
}
}
Expand Down
41 changes: 15 additions & 26 deletions connect/src/state/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use librespot_protocol::player::{Context, ContextIndex, ContextPage, ContextTrac
use std::collections::HashMap;
use uuid::Uuid;

const LOCAL_FILES_IDENTIFIER: &str = "spotify:local-files";

#[derive(Debug, Clone)]
pub struct StateContext {
pub tracks: Vec<ProvidedTrack>,
Expand Down Expand Up @@ -84,6 +86,8 @@ impl ConnectState {
if context.pages.iter().all(|p| p.tracks.is_empty()) {
error!("context didn't have any tracks: {context:#?}");
return Err(StateError::ContextHasNoTracks.into());
} else if context.uri.starts_with(LOCAL_FILES_IDENTIFIER) {
return Err(StateError::UnsupportedLocalPlayBack.into());
}

self.next_contexts.clear();
Expand All @@ -98,7 +102,7 @@ impl ConnectState {
}

let page = match first_page {
None => return Err(StateError::ContextHasNoTracks.into()),
None => Err(StateError::ContextHasNoTracks)?,
Some(p) => p,
};

Expand Down Expand Up @@ -244,39 +248,24 @@ impl ConnectState {
context_uri: Option<&str>,
provider: Option<Provider>,
) -> Result<ProvidedTrack, Error> {
// completely ignore local playback.
if matches!(context_uri, Some(context_uri) if context_uri.starts_with("spotify:local-files"))
{
return Err(StateError::UnsupportedLocalPlayBack.into());
}

let question_mark_idx = ctx_track
.uri
.contains('?')
.then(|| ctx_track.uri.find('?'))
.flatten();
let id = if !ctx_track.uri.is_empty() {
if ctx_track.uri.contains(['?', '%']) {
Err(StateError::InvalidTrackUri(ctx_track.uri.clone()))?
}

let ctx_track_uri = if let Some(idx) = question_mark_idx {
&ctx_track.uri[..idx]
SpotifyId::from_uri(&ctx_track.uri)?
} else if !ctx_track.gid.is_empty() {
SpotifyId::from_raw(&ctx_track.gid)?
} else {
&ctx_track.uri
}
.to_string();
Err(StateError::InvalidTrackUri(String::new()))?
};

let provider = if self.unavailable_uri.contains(&ctx_track_uri) {
let provider = if self.unavailable_uri.contains(&ctx_track.uri) {
Provider::Unavailable
} else {
provider.unwrap_or(Provider::Context)
};

let id = if !ctx_track_uri.is_empty() {
SpotifyId::from_uri(&ctx_track_uri)
} else if !ctx_track.gid.is_empty() {
SpotifyId::from_raw(&ctx_track.gid)
} else {
return Err(Error::unavailable("track not available"));
}?;

// assumption: the uid is used as unique-id of any item
// - queue resorting is done by each client and orients itself by the given uid
// - if no uid is present, resorting doesn't work or behaves not as intended
Expand Down
5 changes: 2 additions & 3 deletions connect/src/state/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ impl ConnectState {
.disallow_toggling_shuffle_reasons
.first()
{
return Err(StateError::CurrentlyDisallowed {
Err(StateError::CurrentlyDisallowed {
action: "shuffle".to_string(),
reason: reason.clone(),
}
.into());
})?
}

self.prev_tracks.clear();
Expand Down
23 changes: 21 additions & 2 deletions core/src/spclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,10 +802,28 @@ impl SpClient {
self.request_url(&url).await
}

/// Request the context for an uri
///
/// ## Query entry found in the wild:
/// - include_video=true
/// ## Remarks:
/// - track
/// - returns a single page with a single track
/// - when requesting a single track with a query in the request, the returned track uri
/// **will** contain the query
/// - artists
/// - returns 2 pages with tracks: 10 most popular tracks and latest/popular album
/// - remaining pages are albums of the artists and are only provided as page_url
/// - search
/// - is massively influenced by the provided query
/// - the query result shown by the search expects no query at all
/// - uri looks like "spotify:search:never+gonna"
pub async fn get_context(&self, uri: &str) -> Result<Context, Error> {
let uri = format!("/context-resolve/v1/{uri}");

let res = self.request(&Method::GET, &uri, None, None).await?;
let res = self
.request_with_options(&Method::GET, &uri, None, None, &NO_METRICS_AND_SALT)
.await?;
let ctx_json = String::from_utf8(res.to_vec())?;
let ctx = protobuf_json_mapping::parse_from_str::<Context>(&ctx_json)?;

Expand All @@ -817,11 +835,12 @@ impl SpClient {
context_request: &AutoplayContextRequest,
) -> Result<Context, Error> {
let res = self
.request_with_protobuf(
.request_with_protobuf_and_options(
&Method::POST,
"/context-resolve/v1/autoplay",
None,
context_request,
&NO_METRICS_AND_SALT,
)
.await?;

Expand Down

0 comments on commit 81020fa

Please sign in to comment.