diff --git a/Cargo.lock b/Cargo.lock index b522f169..0f24802d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/connect/Cargo.toml b/connect/Cargo.toml index 108a3804..1e02df84 100644 --- a/connect/Cargo.toml +++ b/connect/Cargo.toml @@ -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" diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 5705c8c2..857a4409 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -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 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(); - // 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(); - if config.has_volume_ctrl { - repeated.push(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(); - // 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(); - // 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(); - // 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()); - // other known types: - // - "audio/ad" - // - "audio/interruption" - // - "audio/local" - // - "video/ad" - // - "video/episode" - }; - msg - }; - }; - msg - } + 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); + 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.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 { + VOLUME_STEPS + } else { + 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.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.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.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 } 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 { - 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 { - let index: Vec = self.state.get_track()[start_index..] + let index: Vec = 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 - 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()?) diff --git a/core/Cargo.toml b/core/Cargo.toml index 0bd512da..26c4b5b1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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" diff --git a/core/src/authentication.rs b/core/src/authentication.rs index 89117860..655a5311 100644 --- a/core/src/authentication.rs +++ b/core/src/authentication.rs @@ -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(v: &T, ser: S) -> Result 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 where - T: ProtobufEnum, + T: Enum, D: serde::Deserializer<'de>, { let v: i32 = serde::Deserialize::deserialize(de)?; diff --git a/core/src/cdn_url.rs b/core/src/cdn_url.rs index e7016143..39e596a6 100644 --- a/core/src/cdn_url.rs +++ b/core/src/cdn_url.rs @@ -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); @@ -100,14 +100,17 @@ impl CdnUrl { impl TryFrom for MaybeExpiringUrls { type Error = crate::Error; fn try_from(msg: CdnUrlMessage) -> Result { - 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)?; diff --git a/core/src/connection/handshake.rs b/core/src/connection/handshake.rs index b720455c..52b32778 100644 --- a/core/src/connection/handshake.rs +++ b/core/src/connection/handshake.rs @@ -54,16 +54,22 @@ pub async fn handshake( 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(); - as WriteBytesExt>::write_u32::(&mut buffer, size)?; + as WriteBytesExt>::write_u32::(&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(); - as WriteBytesExt>::write_u32::(&mut buffer, size)?; + as WriteBytesExt>::write_u32::(&mut buffer, size.try_into().unwrap())?; packet.write_to_vec(&mut buffer)?; connection.write_all(&buffer[..]).await?; diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index a0ea8b79..e7aa1693 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -58,7 +58,7 @@ impl From for Error { impl From 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) diff --git a/core/src/date.rs b/core/src/date.rs index a3c1b8d7..5f08d4e8 100644 --- a/core/src/date.rs +++ b/core/src/date.rs @@ -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 { // 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))) } } diff --git a/core/src/error.rs b/core/src/error.rs index 87cf3c86..ca58e779 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -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, diff --git a/core/src/file_id.rs b/core/src/file_id.rs index 1e84b489..61b33125 100644 --- a/core/src/file_id.rs +++ b/core/src/file_id.rs @@ -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()) } } diff --git a/core/src/mercury/mod.rs b/core/src/mercury/mod.rs index 44e8de9c..760ea233 100644 --- a/core/src/mercury/mod.rs +++ b/core/src/mercury/mod.rs @@ -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, }; diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 4324eb96..7716fef9 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -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 { + async fn client_token_request(&self, message: &M) -> Result { 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( &self, method: &Method, endpoint: &str, headers: Option, - message: &dyn Message, + message: &M, ) -> SpClientResult { let body = protobuf::text_format::print_to_string(message); diff --git a/core/src/spotify_id.rs b/core/src/spotify_id.rs index da28f98e..f362fed4 100644 --- a/core/src/spotify_id.rs +++ b/core/src/spotify_id.rs @@ -423,12 +423,12 @@ impl TryFrom<&Vec> for SpotifyId { impl TryFrom<&protocol::spirc::TrackRef> for SpotifyId { type Error = crate::Error; fn try_from(track: &protocol::spirc::TrackRef) -> Result { - 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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::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::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::from_base62(picture.get_uri()) + Self::from_base62(picture.uri()) } } diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 4d977bea..9da0ce42 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -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"] } diff --git a/metadata/src/album.rs b/metadata/src/album.rs index 11f13331..8b33571e 100644 --- a/metadata/src/album.rs +++ b/metadata/src/album.rs @@ -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<&::Message> for Album { fn try_from(album: &::Message) -> Result { 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 { 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()?, }) } } diff --git a/metadata/src/artist.rs b/metadata/src/artist.rs index 2f50488b..d3162fc6 100644 --- a/metadata/src/artist.rs +++ b/metadata/src/artist.rs @@ -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<&::Message> for Artist { fn try_from(artist: &::Message) -> Result { 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 { 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 { 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 { - 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()?, }, _ => { diff --git a/metadata/src/audio/file.rs b/metadata/src/audio/file.rs index 08017187..e8fe822b 100644 --- a/metadata/src/audio/file.rs +++ b/metadata/src/audio/file.rs @@ -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 = 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 diff --git a/metadata/src/availability.rs b/metadata/src/availability.rs index 20727f8c..6713da28 100644 --- a/metadata/src/availability.rs +++ b/metadata/src/availability.rs @@ -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 { 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()?, }) } } diff --git a/metadata/src/content_rating.rs b/metadata/src/content_rating.rs index 42d0ad5e..29693e43 100644 --- a/metadata/src/content_rating.rs +++ b/metadata/src/content_rating.rs @@ -22,8 +22,8 @@ impl_deref_wrapped!(ContentRatings, Vec); 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(), } } } diff --git a/metadata/src/copyright.rs b/metadata/src/copyright.rs index 360fd994..5a5ab4db 100644 --- a/metadata/src/copyright.rs +++ b/metadata/src/copyright.rs @@ -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); 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(), } } } diff --git a/metadata/src/episode.rs b/metadata/src/episode.rs index fe795a25..8e200802 100644 --- a/metadata/src/episode.rs +++ b/metadata/src/episode.rs @@ -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<&::Message> for Episode { fn try_from(episode: &::Message) -> Result { 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(), }) } } diff --git a/metadata/src/external_id.rs b/metadata/src/external_id.rs index ce8d4fd8..b5f50993 100644 --- a/metadata/src/external_id.rs +++ b/metadata/src/external_id.rs @@ -22,8 +22,8 @@ impl_deref_wrapped!(ExternalIds, Vec); 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(), } } } diff --git a/metadata/src/image.rs b/metadata/src/image.rs index 0bbe5010..be0137e7 100644 --- a/metadata/src/image.rs +++ b/metadata/src/image.rs @@ -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 { Ok(Self { - target_name: picture.get_target_name().to_owned(), + target_name: picture.target_name().to_owned(), uri: picture.try_into()?, }) } diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index e7263cae..d4cbd4ec 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -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; diff --git a/metadata/src/playlist/annotation.rs b/metadata/src/playlist/annotation.rs index 9cb7f144..d73f7b93 100644 --- a/metadata/src/playlist/annotation.rs +++ b/metadata/src/playlist/annotation.rs @@ -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 { 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<&::Message> for PlaylistAnnotation annotation: &::Message, ) -> Result { 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(), }) } } diff --git a/metadata/src/playlist/attribute.rs b/metadata/src/playlist/attribute.rs index b5849a66..6da2be83 100644 --- a/metadata/src/playlist/attribute.rs +++ b/metadata/src/playlist/attribute.rs @@ -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 { 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 { 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 { 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::>() + .as_slice() + .into(), }) } } @@ -156,8 +157,14 @@ impl TryFrom<&PlaylistPartialItemAttributesMessage> for PlaylistPartialItemAttri type Error = librespot_core::Error; fn try_from(attributes: &PlaylistPartialItemAttributesMessage) -> Result { 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::>() + .as_slice() + .into(), }) } } @@ -166,8 +173,8 @@ impl TryFrom<&PlaylistUpdateAttributesMessage> for PlaylistUpdateAttributes { type Error = librespot_core::Error; fn try_from(update: &PlaylistUpdateAttributesMessage) -> Result { 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 { 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()?, }) } } diff --git a/metadata/src/playlist/diff.rs b/metadata/src/playlist/diff.rs index 4e40d2a5..c4967d85 100644 --- a/metadata/src/playlist/diff.rs +++ b/metadata/src/playlist/diff.rs @@ -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 { 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()?, }) } } diff --git a/metadata/src/playlist/item.rs b/metadata/src/playlist/item.rs index f684f771..4fb892c6 100644 --- a/metadata/src/playlist/item.rs +++ b/metadata/src/playlist/item.rs @@ -58,7 +58,7 @@ impl TryFrom<&PlaylistItemMessage> for PlaylistItem { fn try_from(item: &PlaylistItemMessage) -> Result { 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 { 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 { 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(), }) } } diff --git a/metadata/src/playlist/list.rs b/metadata/src/playlist/list.rs index dcbd9ea1..b4eaf9b6 100644 --- a/metadata/src/playlist/list.rs +++ b/metadata/src/playlist/list.rs @@ -129,7 +129,7 @@ impl Metadata for Playlist { impl TryFrom<&::Message> for SelectedListContent { type Error = librespot_core::Error; fn try_from(playlist: &::Message) -> Result { - 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<&::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::, 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(), + ), }) } } diff --git a/metadata/src/playlist/operation.rs b/metadata/src/playlist/operation.rs index 06264aca..5f7c7551 100644 --- a/metadata/src/playlist/operation.rs +++ b/metadata/src/playlist/operation.rs @@ -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 { 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 { 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 { 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(), }) } } diff --git a/metadata/src/playlist/permission.rs b/metadata/src/playlist/permission.rs index 47ddbdee..ebb179a8 100644 --- a/metadata/src/playlist/permission.rs +++ b/metadata/src/playlist/permission.rs @@ -27,12 +27,18 @@ impl_deref_wrapped!(PermissionLevels, Vec); 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(), } } } diff --git a/metadata/src/restriction.rs b/metadata/src/restriction.rs index 565010ff..df9de425 100644 --- a/metadata/src/restriction.rs +++ b/metadata/src/restriction.rs @@ -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::>() + .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, } diff --git a/metadata/src/sale_period.rs b/metadata/src/sale_period.rs index 911e1d04..a9ee317c 100644 --- a/metadata/src/sale_period.rs +++ b/metadata/src/sale_period.rs @@ -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 { 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()?, }) } } diff --git a/metadata/src/show.rs b/metadata/src/show.rs index 0d3acef8..5c86856c 100644 --- a/metadata/src/show.rs +++ b/metadata/src/show.rs @@ -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<&::Message> for Show { fn try_from(show: &::Message) -> Result { 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(), }) } } diff --git a/metadata/src/track.rs b/metadata/src/track.rs index 03fab92c..dd945bed 100644 --- a/metadata/src/track.rs +++ b/metadata/src/track.rs @@ -73,31 +73,30 @@ impl TryFrom<&::Message> for Track { fn try_from(track: &::Message) -> Result { 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()?, }) } } diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 36d00c2b..5ca462d3 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -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" diff --git a/protocol/build.rs b/protocol/build.rs index b63fa1aa..e1378d37 100644 --- a/protocol/build.rs +++ b/protocol/build.rs @@ -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::>() - .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(); }