librespot_metadata/playlist/
list.rs1use std::{
2 fmt::Debug,
3 ops::{Deref, DerefMut},
4};
5
6use crate::{
7 request::RequestResult,
8 util::{impl_deref_wrapped, impl_from_repeated_copy, impl_try_from_repeated},
9 Metadata,
10};
11
12use super::{
13 attribute::PlaylistAttributes, diff::PlaylistDiff, item::PlaylistItemList,
14 permission::Capabilities,
15};
16
17use librespot_core::{
18 date::Date,
19 spotify_id::{NamedSpotifyId, SpotifyId},
20 Error, Session,
21};
22
23use librespot_protocol as protocol;
24use protocol::playlist4_external::GeoblockBlockingType as Geoblock;
25
26#[derive(Debug, Clone, Default)]
27pub struct Geoblocks(Vec<Geoblock>);
28
29impl_deref_wrapped!(Geoblocks, Vec<Geoblock>);
30
31#[derive(Debug, Clone)]
32pub struct Playlist {
33 pub id: NamedSpotifyId,
34 pub revision: Vec<u8>,
35 pub length: i32,
36 pub attributes: PlaylistAttributes,
37 pub contents: PlaylistItemList,
38 pub diff: Option<PlaylistDiff>,
39 pub sync_result: Option<PlaylistDiff>,
40 pub resulting_revisions: Playlists,
41 pub has_multiple_heads: bool,
42 pub is_up_to_date: bool,
43 pub nonces: Vec<i64>,
44 pub timestamp: Date,
45 pub has_abuse_reporting: bool,
46 pub capabilities: Capabilities,
47 pub geoblocks: Geoblocks,
48}
49
50#[derive(Debug, Clone, Default)]
51pub struct Playlists(pub Vec<SpotifyId>);
52
53impl_deref_wrapped!(Playlists, Vec<SpotifyId>);
54
55#[derive(Debug, Clone)]
56pub struct SelectedListContent {
57 pub revision: Vec<u8>,
58 pub length: i32,
59 pub attributes: PlaylistAttributes,
60 pub contents: PlaylistItemList,
61 pub diff: Option<PlaylistDiff>,
62 pub sync_result: Option<PlaylistDiff>,
63 pub resulting_revisions: Playlists,
64 pub has_multiple_heads: bool,
65 pub is_up_to_date: bool,
66 pub nonces: Vec<i64>,
67 pub timestamp: Date,
68 pub owner_username: String,
69 pub has_abuse_reporting: bool,
70 pub capabilities: Capabilities,
71 pub geoblocks: Geoblocks,
72}
73
74impl Playlist {
75 pub fn tracks(&self) -> impl ExactSizeIterator<Item = &SpotifyId> {
76 let tracks = self.contents.items.iter().map(|item| &item.id);
77
78 let length = tracks.len();
79 let expected_length = self.length as usize;
80 if length != expected_length {
81 warn!(
82 "Got {} tracks, but the list should contain {} tracks.",
83 length, expected_length,
84 );
85 }
86
87 tracks
88 }
89
90 pub fn name(&self) -> &str {
91 &self.attributes.name
92 }
93}
94
95#[async_trait]
96impl Metadata for Playlist {
97 type Message = protocol::playlist4_external::SelectedListContent;
98
99 async fn request(session: &Session, playlist_id: &SpotifyId) -> RequestResult {
100 session.spclient().get_playlist(playlist_id).await
101 }
102
103 fn parse(msg: &Self::Message, id: &SpotifyId) -> Result<Self, Error> {
104 let playlist = SelectedListContent::try_from(msg)?;
106 let id = NamedSpotifyId::from_spotify_id(*id, &playlist.owner_username);
107
108 Ok(Self {
109 id,
110 revision: playlist.revision,
111 length: playlist.length,
112 attributes: playlist.attributes,
113 contents: playlist.contents,
114 diff: playlist.diff,
115 sync_result: playlist.sync_result,
116 resulting_revisions: playlist.resulting_revisions,
117 has_multiple_heads: playlist.has_multiple_heads,
118 is_up_to_date: playlist.is_up_to_date,
119 nonces: playlist.nonces,
120 timestamp: playlist.timestamp,
121 has_abuse_reporting: playlist.has_abuse_reporting,
122 capabilities: playlist.capabilities,
123 geoblocks: playlist.geoblocks,
124 })
125 }
126}
127
128impl TryFrom<&<Playlist as Metadata>::Message> for SelectedListContent {
129 type Error = librespot_core::Error;
130 fn try_from(playlist: &<Playlist as Metadata>::Message) -> Result<Self, Self::Error> {
131 let timestamp = playlist.timestamp();
132 let timestamp = if timestamp > 9295169800000 {
133 warn!("timestamp is very large; assuming it's in microseconds");
141 timestamp / 1000
142 } else {
143 timestamp
144 };
145 let timestamp = Date::from_timestamp_ms(timestamp)?;
146
147 Ok(Self {
148 revision: playlist.revision().to_owned(),
149 length: playlist.length(),
150 attributes: playlist.attributes.get_or_default().try_into()?,
151 contents: playlist.contents.get_or_default().try_into()?,
152 diff: playlist.diff.as_ref().map(TryInto::try_into).transpose()?,
153 sync_result: playlist
154 .sync_result
155 .as_ref()
156 .map(TryInto::try_into)
157 .transpose()?,
158 resulting_revisions: Playlists(
159 playlist
160 .resulting_revisions
161 .iter()
162 .map(|p| p.try_into())
163 .collect::<Result<Vec<SpotifyId>, Error>>()?,
164 ),
165 has_multiple_heads: playlist.multiple_heads(),
166 is_up_to_date: playlist.up_to_date(),
167 nonces: playlist.nonces.clone(),
168 timestamp,
169 owner_username: playlist.owner_username().to_owned(),
170 has_abuse_reporting: playlist.abuse_reporting_enabled(),
171 capabilities: playlist.capabilities.get_or_default().into(),
172 geoblocks: Geoblocks(
173 playlist
174 .geoblock
175 .iter()
176 .map(|b| b.enum_value_or_default())
177 .collect(),
178 ),
179 })
180 }
181}
182
183impl_from_repeated_copy!(Geoblock, Geoblocks);
184impl_try_from_repeated!(Vec<u8>, Playlists);