1use anyhow::bail;
10
11use gst::glib;
12use gst::prelude::*;
13
14use futures::future::{AbortHandle, Aborted};
15use librespot_core::{
16 authentication::Credentials, cache::Cache, config::SessionConfig, session::Session, SpotifyUri,
17};
18
19#[derive(Default, Debug, Clone)]
20pub struct Settings {
21 access_token: String,
22 cache_credentials: String,
23 cache_files: String,
24 cache_max_size: u64,
25 pub track: String,
26}
27
28impl Settings {
29 pub fn properties() -> Vec<glib::ParamSpec> {
30 vec![
31 glib::ParamSpecString::builder("access-token")
32 .nick("Access token")
33 .blurb("Spotify access token, requires 'streaming' scope")
34 .default_value(Some(""))
35 .mutable_ready()
36 .build(),
37 glib::ParamSpecString::builder("cache-credentials")
38 .nick("Credentials cache")
39 .blurb("Directory where to cache Spotify credentials")
40 .default_value(Some(""))
41 .mutable_ready()
42 .build(),
43 glib::ParamSpecString::builder("cache-files")
44 .nick("Files cache")
45 .blurb("Directory where to cache downloaded files from Spotify")
46 .default_value(Some(""))
47 .mutable_ready()
48 .build(),
49 glib::ParamSpecUInt64::builder("cache-max-size")
50 .nick("Cache max size")
51 .blurb(
52 "The max allowed size of the cache, in bytes, or 0 to disable the cache limit",
53 )
54 .default_value(0)
55 .mutable_ready()
56 .build(),
57 glib::ParamSpecString::builder("track")
58 .nick("Spotify URI")
59 .blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'")
60 .default_value(Some(""))
61 .mutable_ready()
62 .build(),
63 ]
64 }
65
66 pub fn set_property(&mut self, value: &glib::Value, pspec: &glib::ParamSpec) {
67 match pspec.name() {
68 "access-token" => {
69 self.access_token = value.get().expect("type checked upstream");
70 }
71 "cache-credentials" => {
72 self.cache_credentials = value.get().expect("type checked upstream");
73 }
74 "cache-files" => {
75 self.cache_files = value.get().expect("type checked upstream");
76 }
77 "cache-max-size" => {
78 self.cache_max_size = value.get().expect("type checked upstream");
79 }
80 "track" => {
81 self.track = value.get().expect("type checked upstream");
82 }
83 _ => unimplemented!(),
84 }
85 }
86
87 pub fn property(&self, pspec: &glib::ParamSpec) -> glib::Value {
88 match pspec.name() {
89 "access-token" => self.access_token.to_value(),
90 "cache-credentials" => self.cache_credentials.to_value(),
91 "cache-files" => self.cache_files.to_value(),
92 "cache-max-size" => self.cache_max_size.to_value(),
93 "track" => self.track.to_value(),
94 _ => unimplemented!(),
95 }
96 }
97
98 pub async fn connect_session<T>(
99 &self,
100 src: T,
101 cat: &gst::DebugCategory,
102 ) -> anyhow::Result<Session>
103 where
104 T: IsA<glib::Object>,
105 {
106 let credentials_cache = if self.cache_credentials.is_empty() {
107 None
108 } else {
109 Some(&self.cache_credentials)
110 };
111
112 let files_cache = if self.cache_files.is_empty() {
113 None
114 } else {
115 Some(&self.cache_files)
116 };
117
118 let max_size = if self.cache_max_size != 0 {
119 Some(self.cache_max_size)
120 } else {
121 None
122 };
123
124 let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
125
126 if let Some(cached_cred) = cache.credentials() {
127 let cached_username = cached_cred
128 .username
129 .as_ref()
130 .map_or("UNKNOWN", |s| s.as_str());
131 gst::debug!(
132 cat,
133 obj = &src,
134 "reuse cached credentials for user {}",
135 cached_username
136 );
137
138 let session = Session::new(SessionConfig::default(), Some(cache));
139 session.connect(cached_cred, true).await?;
140 return Ok(session);
141 }
142
143 gst::debug!(
144 cat,
145 obj = &src,
146 "credentials not in cache or cached credentials invalid",
147 );
148
149 if self.access_token.is_empty() {
150 bail!("access-token is not set and credentials are not in cache");
151 }
152
153 let cred = Credentials::with_access_token(&self.access_token);
154
155 let session = Session::new(SessionConfig::default(), Some(cache));
156 session.connect(cred, true).await?;
157
158 Ok(session)
159 }
160
161 pub fn track_uri(&self) -> anyhow::Result<SpotifyUri> {
162 if self.track.is_empty() {
163 bail!("track is not set");
164 }
165 let track = SpotifyUri::from_uri(&self.track)?;
166
167 Ok(track)
168 }
169}
170
171#[derive(Default)]
172pub enum SetupThread {
173 #[default]
174 None,
175 Pending {
176 thread_handle: Option<std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>>,
177 abort_handle: AbortHandle,
178 },
179 Cancelled,
180 Done,
181}
182
183impl SetupThread {
184 pub fn abort(&mut self) {
185 if matches!(self, SetupThread::None | SetupThread::Done) {
187 return;
188 }
189
190 if let SetupThread::Pending {
191 ref abort_handle, ..
192 } = *self
193 {
194 abort_handle.abort();
195 }
196 *self = SetupThread::Cancelled;
197 }
198}