Update protobuf and related crates to 3.x (#1092)

This commit is contained in:
Lukáš Tyrychtr 2023-01-17 21:46:14 +01:00 committed by GitHub
parent 8941495b80
commit 3662302196
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 694 additions and 672 deletions

95
Cargo.lock generated
View file

@ -441,6 +441,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -1545,7 +1551,7 @@ version = "0.5.0-dev"
dependencies = [
"glob",
"protobuf",
"protobuf-codegen-pure",
"protobuf-codegen",
]
[[package]]
@ -2183,36 +2189,62 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.44"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-ident",
]
[[package]]
name = "protobuf"
version = "2.28.0"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
[[package]]
name = "protobuf-codegen"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6"
checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e"
dependencies = [
"protobuf",
"once_cell",
"protobuf-support",
"thiserror",
]
[[package]]
name = "protobuf-codegen-pure"
version = "2.28.0"
name = "protobuf-codegen"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a29399fc94bcd3eeaa951c715f7bea69409b2445356b00519740bcd6ddd865"
checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901"
dependencies = [
"anyhow",
"once_cell",
"protobuf",
"protobuf-codegen",
"protobuf-parse",
"regex",
"tempfile",
"thiserror",
]
[[package]]
name = "protobuf-parse"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49"
dependencies = [
"anyhow",
"indexmap",
"log",
"protobuf",
"protobuf-support",
"tempfile",
"thiserror",
"which",
]
[[package]]
name = "protobuf-support"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372"
dependencies = [
"thiserror",
]
[[package]]
@ -2227,9 +2259,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
@ -2765,9 +2797,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.101"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@ -2838,18 +2870,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@ -3063,9 +3095,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.4"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
@ -3266,6 +3298,17 @@ dependencies = [
"untrusted",
]
[[package]]
name = "which"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -12,7 +12,7 @@ edition = "2021"
form_urlencoded = "1.0"
futures-util = "0.3"
log = "0.4"
protobuf = "2"
protobuf = "3"
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View file

@ -9,7 +9,7 @@ use std::{
use futures_util::{stream::FusedStream, FutureExt, StreamExt};
use protobuf::{self, Message};
use rand::seq::SliceRandom;
use rand::prelude::SliceRandom;
use thiserror::Error;
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
@ -149,7 +149,7 @@ impl From<SpircLoadCommand> for State {
state.set_shuffle(command.shuffle);
state.set_repeat(command.repeat);
state.set_playing_track_index(command.playing_track_index);
state.set_track(command.tracks.into());
state.track = command.tracks;
state
}
}
@ -174,154 +174,93 @@ fn initial_state() -> State {
frame
}
fn int_capability(typ: protocol::spirc::CapabilityType, val: i64) -> protocol::spirc::Capability {
let mut cap = protocol::spirc::Capability::new();
cap.set_typ(typ);
cap.intValue.push(val);
cap
}
fn initial_device_state(config: ConnectConfig) -> DeviceState {
{
let mut msg = DeviceState::new();
msg.set_sw_version(version::SEMVER.to_string());
msg.set_is_active(false);
msg.set_can_play(true);
msg.set_volume(0);
msg.set_name(config.name);
{
let repeated = msg.mut_capabilities();
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kCanBePlayer);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kDeviceType);
{
let repeated = msg.mut_intValue();
repeated.push(config.device_type as i64)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kGaiaEqConnectId);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kCanBePlayer,
1,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kDeviceType,
config.device_type as i64,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kGaiaEqConnectId,
1,
));
// TODO: implement logout
msg.set_typ(protocol::spirc::CapabilityType::kSupportsLogout);
{
let repeated = msg.mut_intValue();
repeated.push(0)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kIsObservable);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kVolumeSteps);
{
let repeated = msg.mut_intValue();
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kSupportsLogout,
0,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kIsObservable,
1,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kVolumeSteps,
if config.has_volume_ctrl {
repeated.push(VOLUME_STEPS)
VOLUME_STEPS
} else {
repeated.push(0)
}
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kSupportsPlaylistV2);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kSupportsExternalEpisodes);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
0
},
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kSupportsPlaylistV2,
1,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kSupportsExternalEpisodes,
1,
));
// TODO: how would such a rename command be triggered? Handle it.
msg.set_typ(protocol::spirc::CapabilityType::kSupportsRename);
{
let repeated = msg.mut_intValue();
repeated.push(1)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kCommandAcks);
{
let repeated = msg.mut_intValue();
repeated.push(0)
};
msg
};
{
let msg = repeated.push_default();
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kSupportsRename,
1,
));
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kCommandAcks,
0,
));
// TODO: does this mean local files or the local network?
// LAN may be an interesting privacy toggle.
msg.set_typ(protocol::spirc::CapabilityType::kRestrictToLocal);
{
let repeated = msg.mut_intValue();
repeated.push(0)
};
msg
};
{
let msg = repeated.push_default();
msg.capabilities.push(int_capability(
protocol::spirc::CapabilityType::kRestrictToLocal,
0,
));
// TODO: what does this hide, or who do we hide from?
// May be an interesting privacy toggle.
msg.set_typ(protocol::spirc::CapabilityType::kHidden);
{
let repeated = msg.mut_intValue();
repeated.push(0)
};
msg
};
{
let msg = repeated.push_default();
msg.set_typ(protocol::spirc::CapabilityType::kSupportedTypes);
{
let repeated = msg.mut_stringValue();
repeated.push("audio/episode".to_string());
repeated.push("audio/episode+track".to_string());
repeated.push("audio/track".to_string());
msg.capabilities
.push(int_capability(protocol::spirc::CapabilityType::kHidden, 0));
let mut supported_types = protocol::spirc::Capability::new();
supported_types.set_typ(protocol::spirc::CapabilityType::kSupportedTypes);
supported_types
.stringValue
.push("audio/episode".to_string());
supported_types
.stringValue
.push("audio/episode+track".to_string());
supported_types.stringValue.push("audio/track".to_string());
// other known types:
// - "audio/ad"
// - "audio/interruption"
// - "audio/local"
// - "video/ad"
// - "video/episode"
};
msg.capabilities.push(supported_types);
msg
};
};
msg
}
}
fn url_encode(bytes: impl AsRef<[u8]>) -> String {
@ -583,8 +522,8 @@ impl SpircTask {
self.session.spclient().get_next_page(&context_uri).await
} else {
// only send previous tracks that were before the current playback position
let current_position = self.state.get_playing_track_index() as usize;
let previous_tracks = self.state.get_track()[..current_position].iter().filter_map(|t| SpotifyId::try_from(t).ok()).collect();
let current_position = self.state.playing_track_index() as usize;
let previous_tracks = self.state.track[..current_position].iter().filter_map(|t| SpotifyId::try_from(t).ok()).collect();
let scope = if self.autoplay_context {
"stations" // this returns a `StationContext` but we deserialize it into a `PageContext`
@ -602,7 +541,7 @@ impl SpircTask {
info!(
"Resolved {:?} tracks from <{:?}>",
context.tracks.len(),
self.state.get_context_uri(),
self.state.context_uri(),
);
Some(context)
}
@ -651,7 +590,7 @@ impl SpircTask {
rx.close()
}
Ok(())
} else if self.device.get_is_active() {
} else if self.device.is_active() {
trace!("Received SpircCommand::{:?}", cmd);
match cmd {
SpircCommand::Play => {
@ -848,16 +787,16 @@ impl SpircTask {
fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) {
trace!("Received attributes update: {:#?}", update);
let attributes: UserAttributes = update
.get_pairs()
.pairs
.iter()
.map(|pair| (pair.get_key().to_owned(), pair.get_value().to_owned()))
.map(|pair| (pair.key().to_owned(), pair.value().to_owned()))
.collect();
self.session.set_user_attributes(attributes)
}
fn handle_user_attributes_mutation(&mut self, mutation: UserAttributesMutation) {
for attribute in mutation.get_fields().iter() {
let key = attribute.get_name();
for attribute in mutation.fields.iter() {
let key = &attribute.name;
if key == "autoplay" && self.session.config().autoplay.is_some() {
trace!("Autoplay override active. Ignoring mutation.");
@ -902,30 +841,29 @@ impl SpircTask {
// First see if this update was intended for us.
let device_id = &self.ident;
let ident = update.get_ident();
let ident = update.ident();
if ident == device_id
|| (!update.get_recipient().is_empty() && !update.get_recipient().contains(device_id))
|| (!update.recipient.is_empty() && !update.recipient.contains(device_id))
{
return Err(SpircError::Ident(ident.to_string()).into());
}
let old_client_id = self.session.client_id();
for entry in update.get_device_state().get_metadata().iter() {
match entry.get_field_type() {
"client_id" => self.session.set_client_id(entry.get_metadata()),
"brand_display_name" => self.session.set_client_brand_name(entry.get_metadata()),
"model_display_name" => self.session.set_client_model_name(entry.get_metadata()),
for entry in update.device_state.metadata.iter() {
match entry.type_() {
"client_id" => self.session.set_client_id(entry.metadata()),
"brand_display_name" => self.session.set_client_brand_name(entry.metadata()),
"model_display_name" => self.session.set_client_model_name(entry.metadata()),
_ => (),
}
}
self.session
.set_client_name(update.get_device_state().get_name());
self.session.set_client_name(update.device_state.name());
let new_client_id = self.session.client_id();
if self.device.get_is_active() && new_client_id != old_client_id {
if self.device.is_active() && new_client_id != old_client_id {
self.player.emit_session_client_changed_event(
new_client_id,
self.session.client_name(),
@ -934,11 +872,11 @@ impl SpircTask {
);
}
match update.get_typ() {
match update.typ() {
MessageType::kMessageTypeHello => self.notify(Some(ident)),
MessageType::kMessageTypeLoad => {
self.handle_load(update.get_state())?;
self.handle_load(update.state.get_or_default())?;
self.notify(None)
}
@ -978,7 +916,7 @@ impl SpircTask {
}
MessageType::kMessageTypeRepeat => {
let repeat = update.get_state().get_repeat();
let repeat = update.state.repeat();
self.state.set_repeat(repeat);
self.player.emit_repeat_changed_event(repeat);
@ -987,11 +925,11 @@ impl SpircTask {
}
MessageType::kMessageTypeShuffle => {
let shuffle = update.get_state().get_shuffle();
let shuffle = update.state.shuffle();
self.state.set_shuffle(shuffle);
if shuffle {
let current_index = self.state.get_playing_track_index();
let tracks = self.state.mut_track();
let current_index = self.state.playing_track_index();
let tracks = &mut self.state.track;
if !tracks.is_empty() {
tracks.swap(0, current_index as usize);
if let Some((_, rest)) = tracks.split_first_mut() {
@ -1007,12 +945,12 @@ impl SpircTask {
}
MessageType::kMessageTypeSeek => {
self.handle_seek(update.get_position());
self.handle_seek(update.position());
self.notify(None)
}
MessageType::kMessageTypeReplace => {
let context_uri = update.get_state().get_context_uri().to_owned();
let context_uri = update.state.context_uri().to_owned();
// completely ignore local playback.
if context_uri.starts_with("spotify:local-files") {
@ -1020,7 +958,7 @@ impl SpircTask {
return Err(SpircError::UnsupportedLocalPlayBack.into());
}
self.update_tracks(update.get_state());
self.update_tracks(update.state.get_or_default());
if let SpircPlayStatus::Playing {
preloading_of_next_track_triggered,
@ -1043,15 +981,14 @@ impl SpircTask {
}
MessageType::kMessageTypeVolume => {
self.set_volume(update.get_volume() as u16);
self.set_volume(update.volume() as u16);
self.notify(None)
}
MessageType::kMessageTypeNotify => {
if self.device.get_is_active()
&& update.get_device_state().get_is_active()
&& self.device.get_became_active_at()
<= update.get_device_state().get_became_active_at()
if self.device.is_active()
&& update.device_state.is_active()
&& self.device.became_active_at() <= update.device_state.became_active_at()
{
self.handle_disconnect();
}
@ -1088,7 +1025,7 @@ impl SpircTask {
);
self.player
.emit_volume_changed_event(self.device.get_volume() as u16);
.emit_volume_changed_event(self.device.volume() as u16);
self.player
.emit_auto_play_changed_event(self.session.autoplay());
@ -1096,19 +1033,17 @@ impl SpircTask {
self.player
.emit_filter_explicit_content_changed_event(self.session.filter_explicit_content());
self.player
.emit_shuffle_changed_event(self.state.get_shuffle());
self.player.emit_shuffle_changed_event(self.state.shuffle());
self.player
.emit_repeat_changed_event(self.state.get_repeat());
self.player.emit_repeat_changed_event(self.state.repeat());
}
fn handle_load(&mut self, state: &State) -> Result<(), Error> {
if !self.device.get_is_active() {
if !self.device.is_active() {
self.handle_activate();
}
let context_uri = state.get_context_uri().to_owned();
let context_uri = state.context_uri().to_owned();
// completely ignore local playback.
if context_uri.starts_with("spotify:local-files") {
@ -1118,9 +1053,9 @@ impl SpircTask {
self.update_tracks(state);
if !self.state.get_track().is_empty() {
let start_playing = state.get_status() == PlayStatus::kPlayStatusPlay;
self.load_track(start_playing, state.get_position_ms());
if !self.state.track.is_empty() {
let start_playing = state.status() == PlayStatus::kPlayStatusPlay;
self.load_track(start_playing, state.position_ms());
} else {
info!("No more tracks left in queue");
self.handle_stop();
@ -1216,11 +1151,9 @@ impl SpircTask {
fn consume_queued_track(&mut self) -> usize {
// Removes current track if it is queued
// Returns the index of the next track
let current_index = self.state.get_playing_track_index() as usize;
if (current_index < self.state.get_track().len())
&& self.state.get_track()[current_index].get_queued()
{
self.state.mut_track().remove(current_index);
let current_index = self.state.playing_track_index() as usize;
if (current_index < self.state.track.len()) && self.state.track[current_index].queued() {
self.state.track.remove(current_index);
current_index
} else {
current_index + 1
@ -1228,7 +1161,7 @@ impl SpircTask {
}
fn preview_next_track(&mut self) -> Option<SpotifyId> {
self.get_track_id_to_play_from_playlist(self.state.get_playing_track_index() + 1)
self.get_track_id_to_play_from_playlist(self.state.playing_track_index() + 1)
.map(|(track_id, _)| track_id)
}
@ -1260,27 +1193,23 @@ impl SpircTask {
let unavailables = self.get_track_index_for_spotify_id(&track_id, 0);
for &index in unavailables.iter() {
let mut unplayable_track_ref = TrackRef::new();
unplayable_track_ref.set_gid(self.state.get_track()[index].get_gid().to_vec());
unplayable_track_ref.set_gid(self.state.track[index].gid().to_vec());
// Misuse context field to flag the track
unplayable_track_ref.set_context(String::from("NonPlayable"));
std::mem::swap(
&mut self.state.mut_track()[index],
&mut unplayable_track_ref,
);
std::mem::swap(&mut self.state.track[index], &mut unplayable_track_ref);
debug!(
"Marked <{:?}> at {:?} as NonPlayable",
self.state.get_track()[index],
index,
self.state.track[index], index,
);
}
self.handle_preload_next_track();
}
fn handle_next(&mut self) {
let context_uri = self.state.get_context_uri().to_owned();
let mut tracks_len = self.state.get_track().len() as u32;
let context_uri = self.state.context_uri().to_owned();
let mut tracks_len = self.state.track.len() as u32;
let mut new_index = self.consume_queued_track() as u32;
let mut continue_playing = self.state.get_status() == PlayStatus::kPlayStatusPlay;
let mut continue_playing = self.state.status() == PlayStatus::kPlayStatusPlay;
let update_tracks =
self.autoplay_context && tracks_len - new_index < CONTEXT_FETCH_THRESHOLD;
@ -1298,7 +1227,7 @@ impl SpircTask {
if let Some(ref context) = self.context {
self.resolve_context = Some(context.next_page_url.to_owned());
self.update_tracks_from_context();
tracks_len = self.state.get_track().len() as u32;
tracks_len = self.state.track.len() as u32;
}
}
@ -1311,12 +1240,12 @@ impl SpircTask {
debug!("Starting autoplay for <{}>", context_uri);
// force reloading the current context with an autoplay context
self.autoplay_context = true;
self.resolve_context = Some(self.state.get_context_uri().to_owned());
self.resolve_context = Some(self.state.context_uri().to_owned());
self.update_tracks_from_context();
self.player.set_auto_normalise_as_album(false);
} else {
new_index = 0;
continue_playing &= self.state.get_repeat();
continue_playing &= self.state.repeat();
debug!("Looping back to start, repeat is {}", continue_playing);
}
}
@ -1342,29 +1271,29 @@ impl SpircTask {
let mut queue_tracks = Vec::new();
{
let queue_index = self.consume_queued_track();
let tracks = self.state.mut_track();
while queue_index < tracks.len() && tracks[queue_index].get_queued() {
let tracks = &mut self.state.track;
while queue_index < tracks.len() && tracks[queue_index].queued() {
queue_tracks.push(tracks.remove(queue_index));
}
}
let current_index = self.state.get_playing_track_index();
let current_index = self.state.playing_track_index();
let new_index = if current_index > 0 {
current_index - 1
} else if self.state.get_repeat() {
self.state.get_track().len() as u32 - 1
} else if self.state.repeat() {
self.state.track.len() as u32 - 1
} else {
0
};
// Reinsert queued tracks after the new playing track.
let mut pos = (new_index + 1) as usize;
for track in queue_tracks {
self.state.mut_track().insert(pos, track);
self.state.track.insert(pos, track);
pos += 1;
}
self.state.set_playing_track_index(new_index);
let start_playing = self.state.get_status() == PlayStatus::kPlayStatusPlay;
let start_playing = self.state.status() == PlayStatus::kPlayStatusPlay;
self.load_track(start_playing, 0);
} else {
self.handle_seek(0);
@ -1372,12 +1301,12 @@ impl SpircTask {
}
fn handle_volume_up(&mut self) {
let volume = (self.device.get_volume() as u16).saturating_add(VOLUME_STEP_SIZE);
let volume = (self.device.volume() as u16).saturating_add(VOLUME_STEP_SIZE);
self.set_volume(volume);
}
fn handle_volume_down(&mut self) {
let volume = (self.device.get_volume() as u16).saturating_sub(VOLUME_STEP_SIZE);
let volume = (self.device.volume() as u16).saturating_sub(VOLUME_STEP_SIZE);
self.set_volume(volume);
}
@ -1404,18 +1333,17 @@ impl SpircTask {
debug!("Adding {:?} tracks from context to frame", new_tracks.len());
let mut track_vec = self.state.take_track().into_vec();
let mut track_vec = self.state.track.clone();
if let Some(head) = track_vec.len().checked_sub(CONTEXT_TRACKS_HISTORY) {
track_vec.drain(0..head);
}
track_vec.extend_from_slice(new_tracks);
self.state
.set_track(protobuf::RepeatedField::from_vec(track_vec));
self.state.track = track_vec;
// Update playing index
if let Some(new_index) = self
.state
.get_playing_track_index()
.playing_track_index()
.checked_sub(CONTEXT_TRACKS_HISTORY as u32)
{
self.state.set_playing_track_index(new_index);
@ -1428,9 +1356,9 @@ impl SpircTask {
fn update_tracks(&mut self, state: &State) {
trace!("State: {:#?}", state);
let index = state.get_playing_track_index();
let context_uri = state.get_context_uri();
let tracks = state.get_track();
let index = state.playing_track_index();
let context_uri = state.context_uri();
let tracks = &state.track;
trace!("Frame has {:?} tracks", tracks.len());
@ -1443,16 +1371,16 @@ impl SpircTask {
.set_auto_normalise_as_album(context_uri.starts_with("spotify:album:"));
self.state.set_playing_track_index(index);
self.state.set_track(tracks.iter().cloned().collect());
self.state.track = tracks.to_vec();
self.state.set_context_uri(context_uri.to_owned());
// has_shuffle/repeat seem to always be true in these replace msgs,
// but to replicate the behaviour of the Android client we have to
// ignore false values.
let state = state;
if state.get_repeat() {
if state.repeat() {
self.state.set_repeat(true);
}
if state.get_shuffle() {
if state.shuffle() {
self.state.set_shuffle(true);
}
}
@ -1463,10 +1391,10 @@ impl SpircTask {
track_id: &SpotifyId,
start_index: usize,
) -> Vec<usize> {
let index: Vec<usize> = self.state.get_track()[start_index..]
let index: Vec<usize> = self.state.track[start_index..]
.iter()
.enumerate()
.filter(|&(_, track_ref)| track_ref.get_gid() == track_id.to_raw())
.filter(|&(_, track_ref)| track_ref.gid() == track_id.to_raw())
.map(|(idx, _)| start_index + idx)
.collect();
index
@ -1474,11 +1402,11 @@ impl SpircTask {
// Broken out here so we can refactor this later when we move to SpotifyObjectID or similar
fn track_ref_is_unavailable(&self, track_ref: &TrackRef) -> bool {
track_ref.get_context() == "NonPlayable"
track_ref.context() == "NonPlayable"
}
fn get_track_id_to_play_from_playlist(&self, index: u32) -> Option<(SpotifyId, u32)> {
let tracks_len = self.state.get_track().len();
let tracks_len = self.state.track.len();
// Guard against tracks_len being zero to prevent
// 'index out of bounds: the len is 0 but the index is 0'
@ -1500,7 +1428,7 @@ impl SpircTask {
// tracks in each frame either have a gid or uri (that may or may not be a valid track)
// E.g - context based frames sometimes contain tracks with <spotify:meta:page:>
let mut track_ref = self.state.get_track()[new_playlist_index].clone();
let mut track_ref = self.state.track[new_playlist_index].clone();
let mut track_id = SpotifyId::try_from(&track_ref);
while self.track_ref_is_unavailable(&track_ref) || track_id.is_err() {
warn!(
@ -1517,7 +1445,7 @@ impl SpircTask {
warn!("No playable track found in state: {:?}", self.state);
return None;
}
track_ref = self.state.get_track()[new_playlist_index].clone();
track_ref = self.state.track[new_playlist_index].clone();
track_id = SpotifyId::try_from(&track_ref);
}
@ -1528,7 +1456,7 @@ impl SpircTask {
}
fn load_track(&mut self, start_playing: bool, position_ms: u32) {
let index = self.state.get_playing_track_index();
let index = self.state.playing_track_index();
match self.get_track_id_to_play_from_playlist(index) {
Some((track, index)) => {
@ -1556,7 +1484,7 @@ impl SpircTask {
}
fn notify(&mut self, recipient: Option<&str>) -> Result<(), Error> {
let status = self.state.get_status();
let status = self.state.status();
// When in loading state, the Spotify UI is disabled for interaction.
// On desktop this isn't so bad but on mobile it means that the bottom
@ -1575,7 +1503,7 @@ impl SpircTask {
}
fn set_volume(&mut self, volume: u16) {
let old_volume = self.device.get_volume();
let old_volume = self.device.volume();
let new_volume = volume as u32;
if old_volume != new_volume {
self.device.set_volume(new_volume);
@ -1583,7 +1511,7 @@ impl SpircTask {
if let Some(cache) = self.session.cache() {
cache.save_volume(volume)
}
if self.device.get_is_active() {
if self.device.is_active() {
self.player.emit_volume_changed_event(volume);
}
}
@ -1612,25 +1540,25 @@ impl<'a> CommandSender<'a> {
frame.set_ident(spirc.ident.clone());
frame.set_seq_nr(spirc.sequence.get());
frame.set_typ(cmd);
frame.set_device_state(spirc.device.clone());
*frame.device_state.mut_or_insert_default() = spirc.device.clone();
frame.set_state_update_id(spirc.now_ms());
CommandSender { spirc, frame }
}
fn recipient(mut self, recipient: &'a str) -> CommandSender<'_> {
self.frame.mut_recipient().push(recipient.to_owned());
self.frame.recipient.push(recipient.to_owned());
self
}
#[allow(dead_code)]
fn state(mut self, state: protocol::spirc::State) -> CommandSender<'a> {
self.frame.set_state(state);
*self.frame.state.mut_or_insert_default() = state;
self
}
fn send(mut self) -> Result<(), Error> {
if !self.frame.has_state() && self.spirc.device.get_is_active() {
self.frame.set_state(self.spirc.state.clone());
if self.frame.state.is_none() && self.spirc.device.is_active() {
*self.frame.state.mut_or_insert_default() = self.spirc.state.clone();
}
self.spirc.sender.send(self.frame.write_to_bytes()?)

View file

@ -41,7 +41,7 @@ once_cell = "1"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
pbkdf2 = { version = "0.11", default-features = false, features = ["hmac"] }
priority-queue = "1.2"
protobuf = "2"
protobuf = "3"
quick-xml = { version = "0.23", features = ["serialize"] }
rand = "0.8"
rsa = "0.6"

View file

@ -4,7 +4,7 @@ use aes::Aes192;
use byteorder::{BigEndian, ByteOrder};
use hmac::Hmac;
use pbkdf2::pbkdf2;
use protobuf::ProtobufEnum;
use protobuf::Enum;
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use thiserror::Error;
@ -145,7 +145,7 @@ impl Credentials {
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
where
T: ProtobufEnum,
T: Enum,
S: serde::Serializer,
{
serde::Serialize::serialize(&v.value(), ser)
@ -153,7 +153,7 @@ where
fn deserialize_protobuf_enum<'de, T, D>(de: D) -> Result<T, D::Error>
where
T: ProtobufEnum,
T: Enum,
D: serde::Deserializer<'de>,
{
let v: i32 = serde::Deserialize::deserialize(de)?;

View file

@ -10,8 +10,8 @@ use url::Url;
use super::{date::Date, Error, FileId, Session};
use librespot_protocol as protocol;
use protocol::storage_resolve::storage_resolve_response::Result as StorageResolveResponse_Result;
use protocol::storage_resolve::StorageResolveResponse as CdnUrlMessage;
use protocol::storage_resolve::StorageResolveResponse_Result;
#[derive(Debug, Clone)]
pub struct MaybeExpiringUrl(pub String, pub Option<Date>);
@ -100,14 +100,17 @@ impl CdnUrl {
impl TryFrom<CdnUrlMessage> for MaybeExpiringUrls {
type Error = crate::Error;
fn try_from(msg: CdnUrlMessage) -> Result<Self, Self::Error> {
if !matches!(msg.get_result(), StorageResolveResponse_Result::CDN) {
if !matches!(
msg.result.enum_value_or_default(),
StorageResolveResponse_Result::CDN
) {
return Err(CdnUrlError::Storage.into());
}
let is_expiring = !msg.get_fileid().is_empty();
let is_expiring = !msg.fileid.is_empty();
let result = msg
.get_cdnurl()
.cdnurl
.iter()
.map(|cdn_url| {
let url = Url::parse(cdn_url)?;

View file

@ -54,16 +54,22 @@ pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>(
let mut accumulator = client_hello(&mut connection, gc).await?;
let message: APResponseMessage = recv_packet(&mut connection, &mut accumulator).await?;
let remote_key = message
.get_challenge()
.get_login_crypto_challenge()
.get_diffie_hellman()
.get_gs()
.challenge
.get_or_default()
.login_crypto_challenge
.get_or_default()
.diffie_hellman
.get_or_default()
.gs()
.to_owned();
let remote_signature = message
.get_challenge()
.get_login_crypto_challenge()
.get_diffie_hellman()
.get_gs_signature()
.challenge
.get_or_default()
.login_crypto_challenge
.get_or_default()
.diffie_hellman
.get_or_default()
.gs_signature()
.to_owned();
// Prevent man-in-the-middle attacks: check server signature
@ -151,35 +157,45 @@ where
let mut packet = ClientHello::new();
packet
.mut_build_info()
.build_info
.mut_or_insert_default()
// ProductInfo won't push autoplay and perhaps other settings
// when set to anything else than PRODUCT_CLIENT
.set_product(protocol::keyexchange::Product::PRODUCT_CLIENT);
packet
.mut_build_info()
.mut_product_flags()
.push(PRODUCT_FLAGS);
packet.mut_build_info().set_platform(platform);
.build_info
.mut_or_insert_default()
.product_flags
.push(PRODUCT_FLAGS.into());
packet
.mut_build_info()
.build_info
.mut_or_insert_default()
.set_platform(platform);
packet
.build_info
.mut_or_insert_default()
.set_version(version::SPOTIFY_VERSION);
packet
.mut_cryptosuites_supported()
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON);
.cryptosuites_supported
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON.into());
packet
.mut_login_crypto_hello()
.mut_diffie_hellman()
.login_crypto_hello
.mut_or_insert_default()
.diffie_hellman
.mut_or_insert_default()
.set_gc(gc);
packet
.mut_login_crypto_hello()
.mut_diffie_hellman()
.login_crypto_hello
.mut_or_insert_default()
.diffie_hellman
.mut_or_insert_default()
.set_server_keys_known(1);
packet.set_client_nonce(client_nonce);
packet.set_padding(vec![0x1e]);
let mut buffer = vec![0, 4];
let size = 2 + 4 + packet.compute_size();
<Vec<u8> as WriteBytesExt>::write_u32::<BigEndian>(&mut buffer, size)?;
<Vec<u8> as WriteBytesExt>::write_u32::<BigEndian>(&mut buffer, size.try_into().unwrap())?;
packet.write_to_vec(&mut buffer)?;
connection.write_all(&buffer[..]).await?;
@ -192,15 +208,15 @@ where
{
let mut packet = ClientResponsePlaintext::new();
packet
.mut_login_crypto_response()
.mut_diffie_hellman()
.login_crypto_response
.mut_or_insert_default()
.diffie_hellman
.mut_or_insert_default()
.set_hmac(challenge);
packet.mut_pow_response();
packet.mut_crypto_response();
let mut buffer = vec![];
let size = 4 + packet.compute_size();
<Vec<u8> as WriteBytesExt>::write_u32::<BigEndian>(&mut buffer, size)?;
<Vec<u8> as WriteBytesExt>::write_u32::<BigEndian>(&mut buffer, size.try_into().unwrap())?;
packet.write_to_vec(&mut buffer)?;
connection.write_all(&buffer[..]).await?;

View file

@ -58,7 +58,7 @@ impl From<AuthenticationError> for Error {
impl From<APLoginFailed> for AuthenticationError {
fn from(login_failure: APLoginFailed) -> Self {
Self::LoginFailed(login_failure.get_error_code())
Self::LoginFailed(login_failure.error_code())
}
}
@ -100,25 +100,33 @@ pub async fn authenticate(
let mut packet = ClientResponseEncrypted::new();
packet
.mut_login_credentials()
.login_credentials
.mut_or_insert_default()
.set_username(credentials.username);
packet
.mut_login_credentials()
.login_credentials
.mut_or_insert_default()
.set_typ(credentials.auth_type);
packet
.mut_login_credentials()
.login_credentials
.mut_or_insert_default()
.set_auth_data(credentials.auth_data);
packet.mut_system_info().set_cpu_family(cpu_family);
packet.mut_system_info().set_os(os);
packet
.mut_system_info()
.system_info
.mut_or_insert_default()
.set_cpu_family(cpu_family);
packet.system_info.mut_or_insert_default().set_os(os);
packet
.system_info
.mut_or_insert_default()
.set_system_information_string(format!(
"librespot-{}-{}",
version::SHA_SHORT,
version::BUILD_ID
));
packet
.mut_system_info()
.system_info
.mut_or_insert_default()
.set_device_id(device_id.to_string());
packet.set_version_string(format!("librespot {}", version::SEMVER));
@ -136,9 +144,9 @@ pub async fn authenticate(
let welcome_data = APWelcome::parse_from_bytes(data.as_ref())?;
let reusable_credentials = Credentials {
username: welcome_data.get_canonical_username().to_owned(),
auth_type: welcome_data.get_reusable_auth_credentials_type(),
auth_data: welcome_data.get_reusable_auth_credentials().to_owned(),
username: welcome_data.canonical_username().to_owned(),
auth_type: welcome_data.reusable_auth_credentials_type(),
auth_data: welcome_data.reusable_auth_credentials().to_owned(),
};
Ok(reusable_credentials)

View file

@ -1,8 +1,4 @@
use std::{
convert::{TryFrom, TryInto},
fmt::Debug,
ops::Deref,
};
use std::{convert::TryFrom, fmt::Debug, ops::Deref};
use time::{
error::ComponentRange, format_description::well_known::Iso8601, Date as _Date, OffsetDateTime,
@ -63,21 +59,17 @@ impl TryFrom<&DateMessage> for Date {
fn try_from(msg: &DateMessage) -> Result<Self, Self::Error> {
// Some metadata contains a year, but no month. In that case just set January.
let month = if msg.has_month() {
msg.get_month() as u8
msg.month() as u8
} else {
1
};
// Having no day will work, but may be unexpected: it will imply the last day
// of the month before. So prevent that, and just set day 1.
let day = if msg.has_day() {
msg.get_day() as u8
} else {
1
};
let day = if msg.has_day() { msg.day() as u8 } else { 1 };
let date = _Date::from_calendar_date(msg.get_year(), month.try_into()?, day)?;
let time = Time::from_hms(msg.get_hour() as u8, msg.get_minute() as u8, 0)?;
let date = _Date::from_calendar_date(msg.year(), month.try_into()?, day)?;
let time = Time::from_hms(msg.hour() as u8, msg.minute() as u8, 0)?;
Ok(Self::from_utc(PrimitiveDateTime::new(date, time)))
}
}

View file

@ -12,7 +12,7 @@ use http::{
status::InvalidStatusCode,
uri::{InvalidUri, InvalidUriParts},
};
use protobuf::ProtobufError;
use protobuf::Error as ProtobufError;
use thiserror::Error;
use tokio::sync::{
mpsc::error::SendError, oneshot::error::RecvError, AcquireError, TryAcquireError,

View file

@ -39,18 +39,18 @@ impl From<&[u8]> for FileId {
}
impl From<&protocol::metadata::Image> for FileId {
fn from(image: &protocol::metadata::Image) -> Self {
Self::from(image.get_file_id())
Self::from(image.file_id())
}
}
impl From<&protocol::metadata::AudioFile> for FileId {
fn from(file: &protocol::metadata::AudioFile) -> Self {
Self::from(file.get_file_id())
Self::from(file.file_id())
}
}
impl From<&protocol::metadata::VideoFile> for FileId {
fn from(video: &protocol::metadata::VideoFile) -> Self {
Self::from(video.get_file_id())
Self::from(video.file_id())
}
}

View file

@ -231,8 +231,8 @@ impl MercuryManager {
let header = protocol::mercury::Header::parse_from_bytes(&header_data)?;
let response = MercuryResponse {
uri: header.get_uri().to_string(),
status_code: header.get_status_code(),
uri: header.uri().to_string(),
status_code: header.status_code(),
payload: pending.parts,
};

View file

@ -1,5 +1,4 @@
use std::{
convert::TryInto,
env::consts::OS,
fmt::Write,
time::{Duration, Instant},
@ -14,7 +13,7 @@ use hyper::{
header::{HeaderName, ACCEPT, AUTHORIZATION, CONTENT_TYPE, RANGE},
Body, HeaderMap, Method, Request,
};
use protobuf::{Message, ProtobufEnum};
use protobuf::{Enum, Message, MessageFull};
use rand::RngCore;
use sha1::{Digest, Sha1};
use sysinfo::{System, SystemExt};
@ -150,7 +149,7 @@ impl SpClient {
Ok(())
}
async fn client_token_request(&self, message: &dyn Message) -> Result<Bytes, Error> {
async fn client_token_request<M: Message>(&self, message: &M) -> Result<Bytes, Error> {
let body = message.write_to_bytes()?;
let request = Request::builder()
@ -179,10 +178,11 @@ impl SpClient {
debug!("Client token unavailable or expired, requesting new token.");
let mut request = ClientTokenRequest::new();
request.set_request_type(ClientTokenRequestType::REQUEST_CLIENT_DATA_REQUEST);
request.request_type = ClientTokenRequestType::REQUEST_CLIENT_DATA_REQUEST.into();
let client_data = request.mut_client_data();
client_data.set_client_version(spotify_version());
client_data.client_version = spotify_version();
// Current state of affairs: keymaster ID works on all tested platforms, but may be phased out,
// so it seems a good idea to mimick the real clients. `self.session().client_id()` returns the
@ -194,12 +194,14 @@ impl SpClient {
"macos" | "windows" => self.session().client_id(),
_ => SessionConfig::default().client_id,
};
client_data.set_client_id(client_id);
client_data.client_id = client_id;
let connectivity_data = client_data.mut_connectivity_sdk_data();
connectivity_data.set_device_id(self.session().device_id().to_string());
connectivity_data.device_id = self.session().device_id().to_string();
let platform_data = connectivity_data.mut_platform_specific_data();
let platform_data = connectivity_data
.platform_specific_data
.mut_or_insert_default();
let sys = System::new();
let os_version = sys.os_version().unwrap_or_else(|| String::from("0"));
@ -218,41 +220,41 @@ impl SpClient {
};
let windows_data = platform_data.mut_desktop_windows();
windows_data.set_os_version(os_version);
windows_data.set_os_build(kernel_version);
windows_data.set_platform_id(2);
windows_data.set_unknown_value_6(9);
windows_data.set_image_file_machine(image_file);
windows_data.set_pe_machine(pe);
windows_data.set_unknown_value_10(true);
windows_data.os_version = os_version;
windows_data.os_build = kernel_version;
windows_data.platform_id = 2;
windows_data.unknown_value_6 = 9;
windows_data.image_file_machine = image_file;
windows_data.pe_machine = pe;
windows_data.unknown_value_10 = true;
}
"ios" => {
let ios_data = platform_data.mut_ios();
ios_data.set_user_interface_idiom(0);
ios_data.set_target_iphone_simulator(false);
ios_data.set_hw_machine("iPhone14,5".to_string());
ios_data.set_system_version(os_version);
ios_data.user_interface_idiom = 0;
ios_data.target_iphone_simulator = false;
ios_data.hw_machine = "iPhone14,5".to_string();
ios_data.system_version = os_version;
}
"android" => {
let android_data = platform_data.mut_android();
android_data.set_android_version(os_version);
android_data.set_api_version(31);
android_data.set_device_name("Pixel".to_owned());
android_data.set_model_str("GF5KQ".to_owned());
android_data.set_vendor("Google".to_owned());
android_data.android_version = os_version;
android_data.api_version = 31;
android_data.device_name = "Pixel".to_owned();
android_data.model_str = "GF5KQ".to_owned();
android_data.vendor = "Google".to_owned();
}
"macos" => {
let macos_data = platform_data.mut_desktop_macos();
macos_data.set_system_version(os_version);
macos_data.set_hw_model("iMac21,1".to_string());
macos_data.set_compiled_cpu_type(std::env::consts::ARCH.to_string());
macos_data.system_version = os_version;
macos_data.hw_model = "iMac21,1".to_string();
macos_data.compiled_cpu_type = std::env::consts::ARCH.to_string();
}
_ => {
let linux_data = platform_data.mut_desktop_linux();
linux_data.set_system_name("Linux".to_string());
linux_data.set_system_release(kernel_version);
linux_data.set_system_version(os_version);
linux_data.set_hardware(std::env::consts::ARCH.to_string());
linux_data.system_name = "Linux".to_string();
linux_data.system_release = kernel_version;
linux_data.system_version = os_version;
linux_data.hardware = std::env::consts::ARCH.to_string();
}
}
@ -272,10 +274,10 @@ impl SpClient {
Some(ClientTokenResponseType::RESPONSE_CHALLENGES_RESPONSE) => {
debug!("Received a hash cash challenge, solving...");
let challenges = message.get_challenges().clone();
let state = challenges.get_state();
let challenges = message.challenges().clone();
let state = challenges.state;
if let Some(challenge) = challenges.challenges.first() {
let hash_cash_challenge = challenge.get_evaluate_hashcash_parameters();
let hash_cash_challenge = challenge.evaluate_hashcash_parameters();
let ctx = vec![];
let prefix = hex::decode(&hash_cash_challenge.prefix).map_err(|e| {
@ -295,15 +297,16 @@ impl SpClient {
let suffix = hex::encode(suffix).to_uppercase();
let mut answer_message = ClientTokenRequest::new();
answer_message.set_request_type(
ClientTokenRequestType::REQUEST_CHALLENGE_ANSWERS_REQUEST,
);
answer_message.request_type =
ClientTokenRequestType::REQUEST_CHALLENGE_ANSWERS_REQUEST
.into();
let challenge_answers = answer_message.mut_challenge_answers();
let mut challenge_answer = ChallengeAnswer::new();
challenge_answer.mut_hash_cash().suffix = suffix.to_string();
challenge_answer.ChallengeType = ChallengeType::CHALLENGE_HASH_CASH;
challenge_answer.ChallengeType =
ChallengeType::CHALLENGE_HASH_CASH.into();
challenge_answers.state = state.to_string();
challenge_answers.answers.push(challenge_answer);
@ -355,21 +358,21 @@ impl SpClient {
}
};
let granted_token = token_response.get_granted_token();
let access_token = granted_token.get_token().to_owned();
let granted_token = token_response.granted_token();
let access_token = granted_token.token.to_owned();
self.lock(|inner| {
let client_token = Token {
access_token: access_token.clone(),
expires_in: Duration::from_secs(
granted_token
.get_refresh_after_seconds()
.refresh_after_seconds
.try_into()
.unwrap_or(7200),
),
token_type: "client-token".to_string(),
scopes: granted_token
.get_domains()
.domains
.iter()
.map(|d| d.domain.clone())
.collect(),
@ -384,12 +387,12 @@ impl SpClient {
Ok(access_token)
}
pub async fn request_with_protobuf(
pub async fn request_with_protobuf<M: Message + MessageFull>(
&self,
method: &Method,
endpoint: &str,
headers: Option<HeaderMap>,
message: &dyn Message,
message: &M,
) -> SpClientResult {
let body = protobuf::text_format::print_to_string(message);

View file

@ -423,12 +423,12 @@ impl TryFrom<&Vec<u8>> for SpotifyId {
impl TryFrom<&protocol::spirc::TrackRef> for SpotifyId {
type Error = crate::Error;
fn try_from(track: &protocol::spirc::TrackRef) -> Result<Self, Self::Error> {
match SpotifyId::from_raw(track.get_gid()) {
match SpotifyId::from_raw(track.gid()) {
Ok(mut id) => {
id.item_type = SpotifyItemType::Track;
Ok(id)
}
Err(_) => SpotifyId::from_uri(track.get_uri()),
Err(_) => SpotifyId::from_uri(track.uri()),
}
}
}
@ -438,7 +438,7 @@ impl TryFrom<&protocol::metadata::Album> for SpotifyId {
fn try_from(album: &protocol::metadata::Album) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Album,
..Self::from_raw(album.get_gid())?
..Self::from_raw(album.gid())?
})
}
}
@ -448,7 +448,7 @@ impl TryFrom<&protocol::metadata::Artist> for SpotifyId {
fn try_from(artist: &protocol::metadata::Artist) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Artist,
..Self::from_raw(artist.get_gid())?
..Self::from_raw(artist.gid())?
})
}
}
@ -458,7 +458,7 @@ impl TryFrom<&protocol::metadata::Episode> for SpotifyId {
fn try_from(episode: &protocol::metadata::Episode) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Episode,
..Self::from_raw(episode.get_gid())?
..Self::from_raw(episode.gid())?
})
}
}
@ -468,7 +468,7 @@ impl TryFrom<&protocol::metadata::Track> for SpotifyId {
fn try_from(track: &protocol::metadata::Track) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Track,
..Self::from_raw(track.get_gid())?
..Self::from_raw(track.gid())?
})
}
}
@ -478,7 +478,7 @@ impl TryFrom<&protocol::metadata::Show> for SpotifyId {
fn try_from(show: &protocol::metadata::Show) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Show,
..Self::from_raw(show.get_gid())?
..Self::from_raw(show.gid())?
})
}
}
@ -488,7 +488,7 @@ impl TryFrom<&protocol::metadata::ArtistWithRole> for SpotifyId {
fn try_from(artist: &protocol::metadata::ArtistWithRole) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Artist,
..Self::from_raw(artist.get_artist_gid())?
..Self::from_raw(artist.artist_gid())?
})
}
}
@ -498,7 +498,7 @@ impl TryFrom<&protocol::playlist4_external::Item> for SpotifyId {
fn try_from(item: &protocol::playlist4_external::Item) -> Result<Self, Self::Error> {
Ok(Self {
item_type: SpotifyItemType::Track,
..Self::from_uri(item.get_uri())?
..Self::from_uri(item.uri())?
})
}
}
@ -508,7 +508,7 @@ impl TryFrom<&protocol::playlist4_external::Item> for SpotifyId {
impl TryFrom<&protocol::playlist4_external::MetaItem> for SpotifyId {
type Error = crate::Error;
fn try_from(item: &protocol::playlist4_external::MetaItem) -> Result<Self, Self::Error> {
Self::try_from(item.get_revision())
Self::try_from(item.revision())
}
}
@ -518,7 +518,7 @@ impl TryFrom<&protocol::playlist4_external::SelectedListContent> for SpotifyId {
fn try_from(
playlist: &protocol::playlist4_external::SelectedListContent,
) -> Result<Self, Self::Error> {
Self::try_from(playlist.get_revision())
Self::try_from(playlist.revision())
}
}
@ -530,7 +530,7 @@ impl TryFrom<&protocol::playlist_annotate3::TranscodedPicture> for SpotifyId {
fn try_from(
picture: &protocol::playlist_annotate3::TranscodedPicture,
) -> Result<Self, Self::Error> {
Self::from_base62(picture.get_uri())
Self::from_base62(picture.uri())
}
}

View file

@ -13,7 +13,7 @@ async-trait = "0.1"
byteorder = "1"
bytes = "1"
log = "0.4"
protobuf = "2"
protobuf = "3"
thiserror = "1"
uuid = { version = "1", default-features = false }
serde = { version = "1.0", features = ["derive"] }

View file

@ -21,7 +21,7 @@ use crate::{
use librespot_core::{date::Date, Error, Session, SpotifyId};
use librespot_protocol as protocol;
pub use protocol::metadata::Album_Type as AlbumType;
pub use protocol::metadata::album::Type as AlbumType;
use protocol::metadata::Disc as DiscMessage;
#[derive(Debug, Clone)]
@ -90,26 +90,26 @@ impl TryFrom<&<Self as Metadata>::Message> for Album {
fn try_from(album: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
Ok(Self {
id: album.try_into()?,
name: album.get_name().to_owned(),
artists: album.get_artist().try_into()?,
album_type: album.get_field_type(),
label: album.get_label().to_owned(),
date: album.get_date().try_into()?,
popularity: album.get_popularity(),
genres: album.get_genre().to_vec(),
covers: album.get_cover_group().into(),
external_ids: album.get_external_id().into(),
discs: album.get_disc().try_into()?,
reviews: album.get_review().to_vec(),
copyrights: album.get_copyright().into(),
restrictions: album.get_restriction().into(),
related: album.get_related().try_into()?,
sale_periods: album.get_sale_period().try_into()?,
cover_group: album.get_cover_group().get_image().into(),
original_title: album.get_original_title().to_owned(),
version_title: album.get_version_title().to_owned(),
type_str: album.get_type_str().to_owned(),
availability: album.get_availability().try_into()?,
name: album.name().to_owned(),
artists: album.artist.as_slice().try_into()?,
album_type: album.type_(),
label: album.label().to_owned(),
date: album.date.get_or_default().try_into()?,
popularity: album.popularity(),
genres: album.genre.to_vec(),
covers: album.cover_group.get_or_default().into(),
external_ids: album.external_id.as_slice().into(),
discs: album.disc.as_slice().try_into()?,
reviews: album.review.to_vec(),
copyrights: album.copyright.as_slice().into(),
restrictions: album.restriction.as_slice().into(),
related: album.related.as_slice().try_into()?,
sale_periods: album.sale_period.as_slice().try_into()?,
cover_group: album.cover_group.image.as_slice().into(),
original_title: album.original_title().to_owned(),
version_title: album.version_title().to_owned(),
type_str: album.type_str().to_owned(),
availability: album.availability.as_slice().try_into()?,
})
}
}
@ -120,9 +120,9 @@ impl TryFrom<&DiscMessage> for Disc {
type Error = librespot_core::Error;
fn try_from(disc: &DiscMessage) -> Result<Self, Self::Error> {
Ok(Self {
number: disc.get_number(),
name: disc.get_name().to_owned(),
tracks: disc.get_track().try_into()?,
number: disc.number(),
name: disc.name().to_owned(),
tracks: disc.track.as_slice().try_into()?,
})
}
}

View file

@ -20,7 +20,7 @@ use crate::{
use librespot_core::{Error, Session, SpotifyId};
use librespot_protocol as protocol;
pub use protocol::metadata::ArtistWithRole_ArtistRole as ArtistRole;
pub use protocol::metadata::artist_with_role::ArtistRole;
use protocol::metadata::ActivityPeriod as ActivityPeriodMessage;
use protocol::metadata::AlbumGroup as AlbumGroupMessage;
@ -187,24 +187,29 @@ impl TryFrom<&<Self as Metadata>::Message> for Artist {
fn try_from(artist: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
Ok(Self {
id: artist.try_into()?,
name: artist.get_name().to_owned(),
popularity: artist.get_popularity(),
top_tracks: artist.get_top_track().try_into()?,
albums: artist.get_album_group().try_into()?,
singles: artist.get_single_group().try_into()?,
compilations: artist.get_compilation_group().try_into()?,
appears_on_albums: artist.get_appears_on_group().try_into()?,
genre: artist.get_genre().to_vec(),
external_ids: artist.get_external_id().into(),
portraits: artist.get_portrait().into(),
biographies: artist.get_biography().into(),
activity_periods: artist.get_activity_period().try_into()?,
restrictions: artist.get_restriction().into(),
related: artist.get_related().try_into()?,
is_portrait_album_cover: artist.get_is_portrait_album_cover(),
portrait_group: artist.get_portrait_group().get_image().into(),
sales_periods: artist.get_sale_period().try_into()?,
availabilities: artist.get_availability().try_into()?,
name: artist.name().to_owned(),
popularity: artist.popularity(),
top_tracks: artist.top_track.as_slice().try_into()?,
albums: artist.album_group.as_slice().try_into()?,
singles: artist.single_group.as_slice().try_into()?,
compilations: artist.compilation_group.as_slice().try_into()?,
appears_on_albums: artist.appears_on_group.as_slice().try_into()?,
genre: artist.genre.to_vec(),
external_ids: artist.external_id.as_slice().into(),
portraits: artist.portrait.as_slice().into(),
biographies: artist.biography.as_slice().into(),
activity_periods: artist.activity_period.as_slice().try_into()?,
restrictions: artist.restriction.as_slice().into(),
related: artist.related.as_slice().try_into()?,
is_portrait_album_cover: artist.is_portrait_album_cover(),
portrait_group: artist
.portrait_group
.get_or_default()
.image
.as_slice()
.into(),
sales_periods: artist.sale_period.as_slice().try_into()?,
availabilities: artist.availability.as_slice().try_into()?,
})
}
}
@ -216,8 +221,8 @@ impl TryFrom<&ArtistWithRoleMessage> for ArtistWithRole {
fn try_from(artist_with_role: &ArtistWithRoleMessage) -> Result<Self, Self::Error> {
Ok(Self {
id: artist_with_role.try_into()?,
name: artist_with_role.get_artist_name().to_owned(),
role: artist_with_role.get_role(),
name: artist_with_role.artist_name().to_owned(),
role: artist_with_role.role(),
})
}
}
@ -228,8 +233,8 @@ impl TryFrom<&TopTracksMessage> for TopTracks {
type Error = librespot_core::Error;
fn try_from(top_tracks: &TopTracksMessage) -> Result<Self, Self::Error> {
Ok(Self {
country: top_tracks.get_country().to_owned(),
tracks: top_tracks.get_track().try_into()?,
country: top_tracks.country().to_owned(),
tracks: top_tracks.track.as_slice().try_into()?,
})
}
}
@ -239,7 +244,7 @@ impl_try_from_repeated!(TopTracksMessage, CountryTopTracks);
impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
type Error = librespot_core::Error;
fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
Ok(Self(album_groups.get_album().try_into()?))
Ok(Self(album_groups.album.as_slice().try_into()?))
}
}
@ -257,14 +262,14 @@ impl_try_from_repeated!(AlbumGroupMessage, AlbumGroups);
impl From<&BiographyMessage> for Biography {
fn from(biography: &BiographyMessage) -> Self {
let portrait_group = biography
.get_portrait_group()
.portrait_group
.iter()
.map(|it| it.get_image().into())
.map(|it| it.image.as_slice().into())
.collect();
Self {
text: biography.get_text().to_owned(),
portraits: biography.get_portrait().into(),
text: biography.text().to_owned(),
portraits: biography.portrait.as_slice().into(),
portrait_group,
}
}
@ -282,11 +287,11 @@ impl TryFrom<&ActivityPeriodMessage> for ActivityPeriod {
period.has_end_year(),
) {
// (decade, start_year, end_year)
(true, false, false) => Self::Decade(period.get_decade().try_into()?),
(true, false, false) => Self::Decade(period.decade().try_into()?),
(false, true, closed_period) => Self::Timespan {
start_year: period.get_start_year().try_into()?,
start_year: period.start_year().try_into()?,
end_year: closed_period
.then(|| period.get_end_year().try_into())
.then(|| period.end_year().try_into())
.transpose()?,
},
_ => {

View file

@ -7,8 +7,8 @@ use std::{
use librespot_core::FileId;
use librespot_protocol as protocol;
pub use protocol::metadata::audio_file::Format as AudioFileFormat;
use protocol::metadata::AudioFile as AudioFileMessage;
pub use protocol::metadata::AudioFile_Format as AudioFileFormat;
use crate::util::impl_deref_wrapped;
@ -45,12 +45,12 @@ impl AudioFiles {
impl From<&[AudioFileMessage]> for AudioFiles {
fn from(files: &[AudioFileMessage]) -> Self {
let audio_files = files
let audio_files: HashMap<AudioFileFormat, FileId> = files
.iter()
.filter_map(|file| {
let file_id = FileId::from(file.get_file_id());
let file_id = FileId::from(file.file_id());
if file.has_format() {
Some((file.get_format(), file_id))
Some((file.format(), file_id))
} else {
trace!("Ignoring file <{}> with unspecified format", file_id);
None

View file

@ -1,5 +1,5 @@
use std::{
convert::{TryFrom, TryInto},
convert::TryFrom,
fmt::Debug,
ops::{Deref, DerefMut},
};
@ -42,8 +42,8 @@ impl TryFrom<&AvailabilityMessage> for Availability {
type Error = librespot_core::Error;
fn try_from(availability: &AvailabilityMessage) -> Result<Self, Self::Error> {
Ok(Self {
catalogue_strs: availability.get_catalogue_str().to_vec(),
start: availability.get_start().try_into()?,
catalogue_strs: availability.catalogue_str.to_vec(),
start: availability.start.get_or_default().try_into()?,
})
}
}

View file

@ -22,8 +22,8 @@ impl_deref_wrapped!(ContentRatings, Vec<ContentRating>);
impl From<&ContentRatingMessage> for ContentRating {
fn from(content_rating: &ContentRatingMessage) -> Self {
Self {
country: content_rating.get_country().to_owned(),
tags: content_rating.get_tag().to_vec(),
country: content_rating.country().to_owned(),
tags: content_rating.tag.to_vec(),
}
}
}

View file

@ -6,8 +6,8 @@ use std::{
use crate::util::{impl_deref_wrapped, impl_from_repeated};
use librespot_protocol as protocol;
pub use protocol::metadata::copyright::Type as CopyrightType;
use protocol::metadata::Copyright as CopyrightMessage;
pub use protocol::metadata::Copyright_Type as CopyrightType;
#[derive(Debug, Clone)]
pub struct Copyright {
@ -23,8 +23,8 @@ impl_deref_wrapped!(Copyrights, Vec<Copyright>);
impl From<&CopyrightMessage> for Copyright {
fn from(copyright: &CopyrightMessage) -> Self {
Self {
copyright_type: copyright.get_field_type(),
text: copyright.get_text().to_owned(),
copyright_type: copyright.type_(),
text: copyright.text().to_owned(),
}
}
}

View file

@ -19,7 +19,7 @@ use crate::{
use librespot_core::{date::Date, Error, Session, SpotifyId};
use librespot_protocol as protocol;
pub use protocol::metadata::Episode_EpisodeType as EpisodeType;
pub use protocol::metadata::episode::EpisodeType;
#[derive(Debug, Clone)]
pub struct Episode {
@ -72,29 +72,29 @@ impl TryFrom<&<Self as Metadata>::Message> for Episode {
fn try_from(episode: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
Ok(Self {
id: episode.try_into()?,
name: episode.get_name().to_owned(),
duration: episode.get_duration().to_owned(),
audio: episode.get_audio().into(),
description: episode.get_description().to_owned(),
number: episode.get_number(),
publish_time: episode.get_publish_time().try_into()?,
covers: episode.get_cover_image().get_image().into(),
language: episode.get_language().to_owned(),
is_explicit: episode.get_explicit().to_owned(),
show_name: episode.get_show().get_name().to_owned(),
videos: episode.get_video().into(),
video_previews: episode.get_video_preview().into(),
audio_previews: episode.get_audio_preview().into(),
restrictions: episode.get_restriction().into(),
freeze_frames: episode.get_freeze_frame().get_image().into(),
keywords: episode.get_keyword().to_vec(),
allow_background_playback: episode.get_allow_background_playback(),
availability: episode.get_availability().try_into()?,
external_url: episode.get_external_url().to_owned(),
episode_type: episode.get_field_type(),
has_music_and_talk: episode.get_music_and_talk(),
content_rating: episode.get_content_rating().into(),
is_audiobook_chapter: episode.get_is_audiobook_chapter(),
name: episode.name().to_owned(),
duration: episode.duration().to_owned(),
audio: episode.audio.as_slice().into(),
description: episode.description().to_owned(),
number: episode.number(),
publish_time: episode.publish_time.get_or_default().try_into()?,
covers: episode.cover_image.image.as_slice().into(),
language: episode.language().to_owned(),
is_explicit: episode.explicit().to_owned(),
show_name: episode.show.name().to_owned(),
videos: episode.video.as_slice().into(),
video_previews: episode.video_preview.as_slice().into(),
audio_previews: episode.audio_preview.as_slice().into(),
restrictions: episode.restriction.as_slice().into(),
freeze_frames: episode.freeze_frame.image.as_slice().into(),
keywords: episode.keyword.to_vec(),
allow_background_playback: episode.allow_background_playback(),
availability: episode.availability.as_slice().try_into()?,
external_url: episode.external_url().to_owned(),
episode_type: episode.type_(),
has_music_and_talk: episode.music_and_talk(),
content_rating: episode.content_rating.as_slice().into(),
is_audiobook_chapter: episode.is_audiobook_chapter(),
})
}
}

View file

@ -22,8 +22,8 @@ impl_deref_wrapped!(ExternalIds, Vec<ExternalId>);
impl From<&ExternalIdMessage> for ExternalId {
fn from(external_id: &ExternalIdMessage) -> Self {
Self {
external_type: external_id.get_field_type().to_owned(),
id: external_id.get_id().to_owned(),
external_type: external_id.type_().to_owned(),
id: external_id.id().to_owned(),
}
}
}

View file

@ -9,9 +9,9 @@ use crate::util::{impl_deref_wrapped, impl_from_repeated, impl_try_from_repeated
use librespot_core::{FileId, SpotifyId};
use librespot_protocol as protocol;
pub use protocol::metadata::image::Size as ImageSize;
use protocol::metadata::Image as ImageMessage;
use protocol::metadata::ImageGroup;
pub use protocol::metadata::Image_Size as ImageSize;
use protocol::playlist4_external::PictureSize as PictureSizeMessage;
use protocol::playlist_annotate3::TranscodedPicture as TranscodedPictureMessage;
@ -60,9 +60,9 @@ impl From<&ImageMessage> for Image {
fn from(image: &ImageMessage) -> Self {
Self {
id: image.into(),
size: image.get_size(),
width: image.get_width(),
height: image.get_height(),
size: image.size(),
width: image.width(),
height: image.height(),
}
}
}
@ -72,8 +72,8 @@ impl_from_repeated!(ImageMessage, Images);
impl From<&PictureSizeMessage> for PictureSize {
fn from(size: &PictureSizeMessage) -> Self {
Self {
target_name: size.get_target_name().to_owned(),
url: size.get_url().to_owned(),
target_name: size.target_name().to_owned(),
url: size.url().to_owned(),
}
}
}
@ -84,7 +84,7 @@ impl TryFrom<&TranscodedPictureMessage> for TranscodedPicture {
type Error = librespot_core::Error;
fn try_from(picture: &TranscodedPictureMessage) -> Result<Self, Self::Error> {
Ok(Self {
target_name: picture.get_target_name().to_owned(),
target_name: picture.target_name().to_owned(),
uri: picture.try_into()?,
})
}

View file

@ -41,7 +41,7 @@ pub use track::Track;
#[async_trait]
pub trait Metadata: Send + Sized + 'static {
type Message: protobuf::Message;
type Message: protobuf::Message + std::fmt::Debug;
// Request a protobuf
async fn request(session: &Session, id: &SpotifyId) -> RequestResult;

View file

@ -1,4 +1,4 @@
use std::convert::{TryFrom, TryInto};
use std::convert::TryFrom;
use std::fmt::Debug;
use protobuf::Message;
@ -34,11 +34,11 @@ impl Metadata for PlaylistAnnotation {
fn parse(msg: &Self::Message, _: &SpotifyId) -> Result<Self, Error> {
Ok(Self {
description: msg.get_description().to_owned(),
picture: msg.get_picture().to_owned(), // TODO: is this a URL or Spotify URI?
transcoded_pictures: msg.get_transcoded_picture().try_into()?,
has_abuse_reporting: msg.get_is_abuse_reporting_enabled(),
abuse_report_state: msg.get_abuse_report_state(),
description: msg.description().to_owned(),
picture: msg.picture().to_owned(), // TODO: is this a URL or Spotify URI?
transcoded_pictures: msg.transcoded_picture.as_slice().try_into()?,
has_abuse_reporting: msg.is_abuse_reporting_enabled(),
abuse_report_state: msg.abuse_report_state(),
})
}
}
@ -77,11 +77,11 @@ impl TryFrom<&<PlaylistAnnotation as Metadata>::Message> for PlaylistAnnotation
annotation: &<PlaylistAnnotation as Metadata>::Message,
) -> Result<Self, Self::Error> {
Ok(Self {
description: annotation.get_description().to_owned(),
picture: annotation.get_picture().to_owned(),
transcoded_pictures: annotation.get_transcoded_picture().try_into()?,
has_abuse_reporting: annotation.get_is_abuse_reporting_enabled(),
abuse_report_state: annotation.get_abuse_report_state(),
description: annotation.description().to_owned(),
picture: annotation.picture().to_owned(),
transcoded_pictures: annotation.transcoded_picture.as_slice().try_into()?,
has_abuse_reporting: annotation.is_abuse_reporting_enabled(),
abuse_report_state: annotation.abuse_report_state(),
})
}
}

View file

@ -1,6 +1,6 @@
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
convert::TryFrom,
fmt::Debug,
ops::{Deref, DerefMut},
};
@ -99,16 +99,16 @@ impl TryFrom<&PlaylistAttributesMessage> for PlaylistAttributes {
type Error = librespot_core::Error;
fn try_from(attributes: &PlaylistAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
name: attributes.get_name().to_owned(),
description: attributes.get_description().to_owned(),
picture: attributes.get_picture().to_owned(),
is_collaborative: attributes.get_collaborative(),
pl3_version: attributes.get_pl3_version().to_owned(),
is_deleted_by_owner: attributes.get_deleted_by_owner(),
client_id: attributes.get_client_id().to_owned(),
format: attributes.get_format().to_owned(),
format_attributes: attributes.get_format_attributes().into(),
picture_sizes: attributes.get_picture_size().into(),
name: attributes.name().to_owned(),
description: attributes.description().to_owned(),
picture: attributes.picture().to_owned(),
is_collaborative: attributes.collaborative(),
pl3_version: attributes.pl3_version().to_owned(),
is_deleted_by_owner: attributes.deleted_by_owner(),
client_id: attributes.client_id().to_owned(),
format: attributes.format().to_owned(),
format_attributes: attributes.format_attributes.as_slice().into(),
picture_sizes: attributes.picture_size.as_slice().into(),
})
}
}
@ -117,12 +117,7 @@ impl From<&[PlaylistFormatAttributeMessage]> for PlaylistFormatAttribute {
fn from(attributes: &[PlaylistFormatAttributeMessage]) -> Self {
let format_attributes = attributes
.iter()
.map(|attribute| {
(
attribute.get_key().to_owned(),
attribute.get_value().to_owned(),
)
})
.map(|attribute| (attribute.key().to_owned(), attribute.value().to_owned()))
.collect();
PlaylistFormatAttribute(format_attributes)
@ -133,12 +128,12 @@ impl TryFrom<&PlaylistItemAttributesMessage> for PlaylistItemAttributes {
type Error = librespot_core::Error;
fn try_from(attributes: &PlaylistItemAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
added_by: attributes.get_added_by().to_owned(),
timestamp: Date::from_timestamp_ms(attributes.get_timestamp())?,
seen_at: Date::from_timestamp_ms(attributes.get_seen_at())?,
is_public: attributes.get_public(),
format_attributes: attributes.get_format_attributes().into(),
item_id: attributes.get_item_id().to_owned(),
added_by: attributes.added_by().to_owned(),
timestamp: Date::from_timestamp_ms(attributes.timestamp())?,
seen_at: Date::from_timestamp_ms(attributes.seen_at())?,
is_public: attributes.public(),
format_attributes: attributes.format_attributes.as_slice().into(),
item_id: attributes.item_id().to_owned(),
})
}
}
@ -146,8 +141,14 @@ impl TryFrom<&PlaylistPartialAttributesMessage> for PlaylistPartialAttributes {
type Error = librespot_core::Error;
fn try_from(attributes: &PlaylistPartialAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
values: attributes.get_values().try_into()?,
no_value: attributes.get_no_value().into(),
values: attributes.values.get_or_default().try_into()?,
no_value: attributes
.no_value
.iter()
.map(|v| v.enum_value_or_default())
.collect::<Vec<PlaylistAttributeKind>>()
.as_slice()
.into(),
})
}
}
@ -156,8 +157,14 @@ impl TryFrom<&PlaylistPartialItemAttributesMessage> for PlaylistPartialItemAttri
type Error = librespot_core::Error;
fn try_from(attributes: &PlaylistPartialItemAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
values: attributes.get_values().try_into()?,
no_value: attributes.get_no_value().into(),
values: attributes.values.get_or_default().try_into()?,
no_value: attributes
.no_value
.iter()
.map(|v| v.enum_value_or_default())
.collect::<Vec<PlaylistItemAttributeKind>>()
.as_slice()
.into(),
})
}
}
@ -166,8 +173,8 @@ impl TryFrom<&PlaylistUpdateAttributesMessage> for PlaylistUpdateAttributes {
type Error = librespot_core::Error;
fn try_from(update: &PlaylistUpdateAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
new_attributes: update.get_new_attributes().try_into()?,
old_attributes: update.get_old_attributes().try_into()?,
new_attributes: update.new_attributes.get_or_default().try_into()?,
old_attributes: update.old_attributes.get_or_default().try_into()?,
})
}
}
@ -176,9 +183,9 @@ impl TryFrom<&PlaylistUpdateItemAttributesMessage> for PlaylistUpdateItemAttribu
type Error = librespot_core::Error;
fn try_from(update: &PlaylistUpdateItemAttributesMessage) -> Result<Self, Self::Error> {
Ok(Self {
index: update.get_index(),
new_attributes: update.get_new_attributes().try_into()?,
old_attributes: update.get_old_attributes().try_into()?,
index: update.index(),
new_attributes: update.new_attributes.get_or_default().try_into()?,
old_attributes: update.old_attributes.get_or_default().try_into()?,
})
}
}

View file

@ -1,7 +1,4 @@
use std::{
convert::{TryFrom, TryInto},
fmt::Debug,
};
use std::{convert::TryFrom, fmt::Debug};
use super::operation::PlaylistOperations;
@ -21,9 +18,19 @@ impl TryFrom<&DiffMessage> for PlaylistDiff {
type Error = librespot_core::Error;
fn try_from(diff: &DiffMessage) -> Result<Self, Self::Error> {
Ok(Self {
from_revision: diff.get_from_revision().try_into()?,
operations: diff.get_ops().try_into()?,
to_revision: diff.get_to_revision().try_into()?,
from_revision: diff
.from_revision
.clone()
.unwrap_or_default()
.as_slice()
.try_into()?,
operations: diff.ops.as_slice().try_into()?,
to_revision: diff
.to_revision
.clone()
.unwrap_or_default()
.as_slice()
.try_into()?,
})
}
}

View file

@ -58,7 +58,7 @@ impl TryFrom<&PlaylistItemMessage> for PlaylistItem {
fn try_from(item: &PlaylistItemMessage) -> Result<Self, Self::Error> {
Ok(Self {
id: item.try_into()?,
attributes: item.get_attributes().try_into()?,
attributes: item.attributes.get_or_default().try_into()?,
})
}
}
@ -69,10 +69,10 @@ impl TryFrom<&PlaylistItemsMessage> for PlaylistItemList {
type Error = librespot_core::Error;
fn try_from(list_items: &PlaylistItemsMessage) -> Result<Self, Self::Error> {
Ok(Self {
position: list_items.get_pos(),
is_truncated: list_items.get_truncated(),
items: list_items.get_items().try_into()?,
meta_items: list_items.get_meta_items().try_into()?,
position: list_items.pos(),
is_truncated: list_items.truncated(),
items: list_items.items.as_slice().try_into()?,
meta_items: list_items.meta_items.as_slice().try_into()?,
})
}
}
@ -82,12 +82,12 @@ impl TryFrom<&PlaylistMetaItemMessage> for PlaylistMetaItem {
fn try_from(item: &PlaylistMetaItemMessage) -> Result<Self, Self::Error> {
Ok(Self {
revision: item.try_into()?,
attributes: item.get_attributes().try_into()?,
length: item.get_length(),
timestamp: Date::from_timestamp_ms(item.get_timestamp())?,
owner_username: item.get_owner_username().to_owned(),
has_abuse_reporting: item.get_abuse_reporting_enabled(),
capabilities: item.get_capabilities().into(),
attributes: item.attributes.get_or_default().try_into()?,
length: item.length(),
timestamp: Date::from_timestamp_ms(item.timestamp())?,
owner_username: item.owner_username().to_owned(),
has_abuse_reporting: item.abuse_reporting_enabled(),
capabilities: item.capabilities.get_or_default().into(),
})
}
}

View file

@ -129,7 +129,7 @@ impl Metadata for Playlist {
impl TryFrom<&<Playlist as Metadata>::Message> for SelectedListContent {
type Error = librespot_core::Error;
fn try_from(playlist: &<Playlist as Metadata>::Message) -> Result<Self, Self::Error> {
let timestamp = playlist.get_timestamp();
let timestamp = playlist.timestamp();
let timestamp = if timestamp > 9295169800000 {
// timestamp is way out of range for milliseconds. Some seem to be in microseconds?
// Observed on playlists where:
@ -146,25 +146,37 @@ impl TryFrom<&<Playlist as Metadata>::Message> for SelectedListContent {
let timestamp = Date::from_timestamp_ms(timestamp)?;
Ok(Self {
revision: playlist.get_revision().to_owned(),
length: playlist.get_length(),
attributes: playlist.get_attributes().try_into()?,
contents: playlist.get_contents().try_into()?,
revision: playlist.revision().to_owned(),
length: playlist.length(),
attributes: playlist.attributes.get_or_default().try_into()?,
contents: playlist.contents.get_or_default().try_into()?,
diff: playlist.diff.as_ref().map(TryInto::try_into).transpose()?,
sync_result: playlist
.sync_result
.as_ref()
.map(TryInto::try_into)
.transpose()?,
resulting_revisions: playlist.get_resulting_revisions().try_into()?,
has_multiple_heads: playlist.get_multiple_heads(),
is_up_to_date: playlist.get_up_to_date(),
nonces: playlist.get_nonces().into(),
resulting_revisions: Playlists(
playlist
.resulting_revisions
.iter()
.map(|p| p.try_into())
.collect::<Result<Vec<SpotifyId>, Error>>()?,
),
has_multiple_heads: playlist.multiple_heads(),
is_up_to_date: playlist.up_to_date(),
nonces: playlist.nonces.clone(),
timestamp,
owner_username: playlist.get_owner_username().to_owned(),
has_abuse_reporting: playlist.get_abuse_reporting_enabled(),
capabilities: playlist.get_capabilities().into(),
geoblocks: playlist.get_geoblock().into(),
owner_username: playlist.owner_username().to_owned(),
has_abuse_reporting: playlist.abuse_reporting_enabled(),
capabilities: playlist.capabilities.get_or_default().into(),
geoblocks: Geoblocks(
playlist
.geoblock
.iter()
.map(|b| b.enum_value_or_default())
.collect(),
),
})
}
}

View file

@ -1,5 +1,5 @@
use std::{
convert::{TryFrom, TryInto},
convert::TryFrom,
fmt::Debug,
ops::{Deref, DerefMut},
};
@ -13,10 +13,10 @@ use crate::{
};
use librespot_protocol as protocol;
pub use protocol::playlist4_external::op::Kind as PlaylistOperationKind;
use protocol::playlist4_external::Add as PlaylistAddMessage;
use protocol::playlist4_external::Mov as PlaylistMoveMessage;
use protocol::playlist4_external::Op as PlaylistOperationMessage;
pub use protocol::playlist4_external::Op_Kind as PlaylistOperationKind;
use protocol::playlist4_external::Rem as PlaylistRemoveMessage;
#[derive(Debug, Clone)]
@ -61,12 +61,18 @@ impl TryFrom<&PlaylistOperationMessage> for PlaylistOperation {
type Error = librespot_core::Error;
fn try_from(operation: &PlaylistOperationMessage) -> Result<Self, Self::Error> {
Ok(Self {
kind: operation.get_kind(),
add: operation.get_add().try_into()?,
rem: operation.get_rem().try_into()?,
mov: operation.get_mov().into(),
update_item_attributes: operation.get_update_item_attributes().try_into()?,
update_list_attributes: operation.get_update_list_attributes().try_into()?,
kind: operation.kind(),
add: operation.add.get_or_default().try_into()?,
rem: operation.rem.get_or_default().try_into()?,
mov: operation.mov.get_or_default().into(),
update_item_attributes: operation
.update_item_attributes
.get_or_default()
.try_into()?,
update_list_attributes: operation
.update_list_attributes
.get_or_default()
.try_into()?,
})
}
}
@ -77,10 +83,10 @@ impl TryFrom<&PlaylistAddMessage> for PlaylistOperationAdd {
type Error = librespot_core::Error;
fn try_from(add: &PlaylistAddMessage) -> Result<Self, Self::Error> {
Ok(Self {
from_index: add.get_from_index(),
items: add.get_items().try_into()?,
add_last: add.get_add_last(),
add_first: add.get_add_first(),
from_index: add.from_index(),
items: add.items.as_slice().try_into()?,
add_last: add.add_last(),
add_first: add.add_first(),
})
}
}
@ -88,9 +94,9 @@ impl TryFrom<&PlaylistAddMessage> for PlaylistOperationAdd {
impl From<&PlaylistMoveMessage> for PlaylistOperationMove {
fn from(mov: &PlaylistMoveMessage) -> Self {
Self {
from_index: mov.get_from_index(),
length: mov.get_length(),
to_index: mov.get_to_index(),
from_index: mov.from_index(),
length: mov.length(),
to_index: mov.to_index(),
}
}
}
@ -99,10 +105,10 @@ impl TryFrom<&PlaylistRemoveMessage> for PlaylistOperationRemove {
type Error = librespot_core::Error;
fn try_from(remove: &PlaylistRemoveMessage) -> Result<Self, Self::Error> {
Ok(Self {
from_index: remove.get_from_index(),
length: remove.get_length(),
items: remove.get_items().try_into()?,
has_items_as_key: remove.get_items_as_key(),
from_index: remove.from_index(),
length: remove.length(),
items: remove.items.as_slice().try_into()?,
has_items_as_key: remove.items_as_key(),
})
}
}

View file

@ -27,12 +27,18 @@ impl_deref_wrapped!(PermissionLevels, Vec<PermissionLevel>);
impl From<&CapabilitiesMessage> for Capabilities {
fn from(playlist: &CapabilitiesMessage) -> Self {
Self {
can_view: playlist.get_can_view(),
can_administrate_permissions: playlist.get_can_administrate_permissions(),
grantable_levels: playlist.get_grantable_level().into(),
can_edit_metadata: playlist.get_can_edit_metadata(),
can_edit_items: playlist.get_can_edit_items(),
can_cancel_membership: playlist.get_can_cancel_membership(),
can_view: playlist.can_view(),
can_administrate_permissions: playlist.can_administrate_permissions(),
grantable_levels: PermissionLevels(
playlist
.grantable_level
.iter()
.map(|l| l.enum_value_or_default())
.collect(),
),
can_edit_metadata: playlist.can_edit_metadata(),
can_edit_items: playlist.can_edit_items(),
can_cancel_membership: playlist.can_cancel_membership(),
}
}
}

View file

@ -9,8 +9,8 @@ use crate::util::{impl_from_repeated, impl_from_repeated_copy};
use protocol::metadata::Restriction as RestrictionMessage;
use librespot_protocol as protocol;
pub use protocol::metadata::Restriction_Catalogue as RestrictionCatalogue;
pub use protocol::metadata::Restriction_Type as RestrictionType;
pub use protocol::metadata::restriction::Catalogue as RestrictionCatalogue;
pub use protocol::metadata::restriction::Type as RestrictionType;
#[derive(Debug, Clone)]
pub struct Restriction {
@ -43,25 +43,30 @@ impl Restriction {
impl From<&RestrictionMessage> for Restriction {
fn from(restriction: &RestrictionMessage) -> Self {
let countries_allowed = if restriction.has_countries_allowed() {
Some(Self::parse_country_codes(
restriction.get_countries_allowed(),
))
Some(Self::parse_country_codes(restriction.countries_allowed()))
} else {
None
};
let countries_forbidden = if restriction.has_countries_forbidden() {
Some(Self::parse_country_codes(
restriction.get_countries_forbidden(),
))
Some(Self::parse_country_codes(restriction.countries_forbidden()))
} else {
None
};
Self {
catalogues: restriction.get_catalogue().into(),
restriction_type: restriction.get_field_type(),
catalogue_strs: restriction.get_catalogue_str().to_vec(),
catalogues: restriction
.catalogue
.iter()
.map(|c| c.enum_value_or_default())
.collect::<Vec<RestrictionCatalogue>>()
.as_slice()
.into(),
restriction_type: restriction
.type_
.unwrap_or_default()
.enum_value_or_default(),
catalogue_strs: restriction.catalogue_str.to_vec(),
countries_allowed,
countries_forbidden,
}

View file

@ -1,5 +1,5 @@
use std::{
convert::{TryFrom, TryInto},
convert::TryFrom,
fmt::Debug,
ops::{Deref, DerefMut},
};
@ -30,9 +30,9 @@ impl TryFrom<&SalePeriodMessage> for SalePeriod {
type Error = librespot_core::Error;
fn try_from(sale_period: &SalePeriodMessage) -> Result<Self, Self::Error> {
Ok(Self {
restrictions: sale_period.get_restriction().into(),
start: sale_period.get_start().try_into()?,
end: sale_period.get_end().try_into()?,
restrictions: sale_period.restriction.as_slice().into(),
start: sale_period.start.get_or_default().try_into()?,
end: sale_period.end.get_or_default().try_into()?,
})
}
}

View file

@ -11,8 +11,8 @@ use crate::{
use librespot_core::{Error, Session, SpotifyId};
use librespot_protocol as protocol;
pub use protocol::metadata::Show_ConsumptionOrder as ShowConsumptionOrder;
pub use protocol::metadata::Show_MediaType as ShowMediaType;
pub use protocol::metadata::show::ConsumptionOrder as ShowConsumptionOrder;
pub use protocol::metadata::show::MediaType as ShowMediaType;
#[derive(Debug, Clone)]
pub struct Show {
@ -53,22 +53,22 @@ impl TryFrom<&<Self as Metadata>::Message> for Show {
fn try_from(show: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
Ok(Self {
id: show.try_into()?,
name: show.get_name().to_owned(),
description: show.get_description().to_owned(),
publisher: show.get_publisher().to_owned(),
language: show.get_language().to_owned(),
is_explicit: show.get_explicit(),
covers: show.get_cover_image().get_image().into(),
episodes: show.get_episode().try_into()?,
copyrights: show.get_copyright().into(),
restrictions: show.get_restriction().into(),
keywords: show.get_keyword().to_vec(),
media_type: show.get_media_type(),
consumption_order: show.get_consumption_order(),
availability: show.get_availability().try_into()?,
trailer_uri: SpotifyId::from_uri(show.get_trailer_uri())?,
has_music_and_talk: show.get_music_and_talk(),
is_audiobook: show.get_is_audiobook(),
name: show.name().to_owned(),
description: show.description().to_owned(),
publisher: show.publisher().to_owned(),
language: show.language().to_owned(),
is_explicit: show.explicit(),
covers: show.cover_image.image.as_slice().into(),
episodes: show.episode.as_slice().try_into()?,
copyrights: show.copyright.as_slice().into(),
restrictions: show.restriction.as_slice().into(),
keywords: show.keyword.to_vec(),
media_type: show.media_type(),
consumption_order: show.consumption_order(),
availability: show.availability.as_slice().try_into()?,
trailer_uri: SpotifyId::from_uri(show.trailer_uri())?,
has_music_and_talk: show.music_and_talk(),
is_audiobook: show.is_audiobook(),
})
}
}

View file

@ -73,31 +73,30 @@ impl TryFrom<&<Self as Metadata>::Message> for Track {
fn try_from(track: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
Ok(Self {
id: track.try_into()?,
name: track.get_name().to_owned(),
album: track.get_album().try_into()?,
artists: track.get_artist().try_into()?,
number: track.get_number(),
disc_number: track.get_disc_number(),
duration: track.get_duration(),
popularity: track.get_popularity(),
is_explicit: track.get_explicit(),
external_ids: track.get_external_id().into(),
restrictions: track.get_restriction().into(),
files: track.get_file().into(),
alternatives: track.get_alternative().try_into()?,
sale_periods: track.get_sale_period().try_into()?,
previews: track.get_preview().into(),
tags: track.get_tags().to_vec(),
earliest_live_timestamp: Date::from_timestamp_ms(track.get_earliest_live_timestamp())?,
has_lyrics: track.get_has_lyrics(),
availability: track.get_availability().try_into()?,
licensor: Uuid::from_slice(track.get_licensor().get_uuid())
.unwrap_or_else(|_| Uuid::nil()),
language_of_performance: track.get_language_of_performance().to_vec(),
content_ratings: track.get_content_rating().into(),
original_title: track.get_original_title().to_owned(),
version_title: track.get_version_title().to_owned(),
artists_with_role: track.get_artist_with_role().try_into()?,
name: track.name().to_owned(),
album: track.album.get_or_default().try_into()?,
artists: track.artist.as_slice().try_into()?,
number: track.number(),
disc_number: track.disc_number(),
duration: track.duration(),
popularity: track.popularity(),
is_explicit: track.explicit(),
external_ids: track.external_id.as_slice().into(),
restrictions: track.restriction.as_slice().into(),
files: track.file.as_slice().into(),
alternatives: track.alternative.as_slice().try_into()?,
sale_periods: track.sale_period.as_slice().try_into()?,
previews: track.preview.as_slice().into(),
tags: track.tags.to_vec(),
earliest_live_timestamp: Date::from_timestamp_ms(track.earliest_live_timestamp())?,
has_lyrics: track.has_lyrics(),
availability: track.availability.as_slice().try_into()?,
licensor: Uuid::from_slice(track.licensor.uuid()).unwrap_or_else(|_| Uuid::nil()),
language_of_performance: track.language_of_performance.to_vec(),
content_ratings: track.content_rating.as_slice().into(),
original_title: track.original_title().to_owned(),
version_title: track.version_title().to_owned(),
artists_with_role: track.artist_with_role.as_slice().try_into()?,
})
}
}

View file

@ -10,8 +10,8 @@ repository = "https://github.com/librespot-org/librespot"
edition = "2021"
[dependencies]
protobuf = "2"
protobuf = "3"
[build-dependencies]
glob = "0.3"
protobuf-codegen-pure = "2"
protobuf-codegen = "3"

View file

@ -46,7 +46,8 @@ fn compile() {
let out_dir = out_dir();
fs::create_dir(&out_dir).expect("create_dir");
protobuf_codegen_pure::Codegen::new()
protobuf_codegen::Codegen::new()
.pure()
.out_dir(&out_dir)
.inputs(&slices)
.include(&proto_dir)
@ -54,26 +55,7 @@ fn compile() {
.expect("Codegen failed.");
}
fn generate_mod_rs() {
let out_dir = out_dir();
let mods = glob::glob(&out_dir.join("*.rs").to_string_lossy())
.expect("glob")
.filter_map(|p| {
p.ok()
.map(|p| format!("pub mod {};", p.file_stem().unwrap().to_string_lossy()))
})
.collect::<Vec<_>>()
.join("\n");
let mod_rs = out_dir.join("mod.rs");
fs::write(&mod_rs, format!("// @generated\n{}\n", mods)).expect("write");
println!("cargo:rustc-env=PROTO_MOD_RS={}", mod_rs.to_string_lossy());
}
fn main() {
cleanup();
compile();
generate_mod_rs();
}