// Copyright (C) 2021 Guillaume Desmottes // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use anyhow::bail; use gst::glib; use gst::prelude::*; use librespot::core::{ cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId, }; use librespot::discovery::Credentials; #[derive(Default, Debug, Clone)] pub struct Settings { username: String, password: String, cache_credentials: String, cache_files: String, cache_max_size: u64, pub track: String, } impl Settings { pub fn properties() -> Vec { vec![glib::ParamSpecString::builder("username") .nick("Username") .blurb("Spotify username, Facebook accounts need a device username from https://www.spotify.com/us/account/set-device-password/") .default_value(Some("")) .mutable_ready() .build(), glib::ParamSpecString::builder("password") .nick("Password") .blurb("Spotify password, Facebook accounts need a device password from https://www.spotify.com/us/account/set-device-password/") .default_value(Some("")) .mutable_ready() .build(), glib::ParamSpecString::builder("cache-credentials") .nick("Credentials cache") .blurb("Directory where to cache Spotify credentials") .default_value(Some("")) .mutable_ready() .build(), glib::ParamSpecString::builder("cache-files") .nick("Files cache") .blurb("Directory where to cache downloaded files from Spotify") .default_value(Some("")) .mutable_ready() .build(), glib::ParamSpecUInt64::builder("cache-max-size") .nick("Cache max size") .blurb("The max allowed size of the cache, in bytes, or 0 to disable the cache limit") .default_value(0) .mutable_ready() .build(), glib::ParamSpecString::builder("track") .nick("Spotify URI") .blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'") .default_value(Some("")) .mutable_ready() .build(), ] } pub fn set_property(&mut self, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "username" => { self.username = value.get().expect("type checked upstream"); } "password" => { self.password = value.get().expect("type checked upstream"); } "cache-credentials" => { self.cache_credentials = value.get().expect("type checked upstream"); } "cache-files" => { self.cache_files = value.get().expect("type checked upstream"); } "cache-max-size" => { self.cache_max_size = value.get().expect("type checked upstream"); } "track" => { self.track = value.get().expect("type checked upstream"); } _ => unimplemented!(), } } pub fn property(&self, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "username" => self.username.to_value(), "password" => self.password.to_value(), "cache-credentials" => self.cache_credentials.to_value(), "cache-files" => self.cache_files.to_value(), "cache-max-size" => self.cache_max_size.to_value(), "track" => self.track.to_value(), _ => unimplemented!(), } } pub async fn connect_session( &self, src: T, cat: &gst::DebugCategory, ) -> anyhow::Result where T: IsA, { let credentials_cache = if self.cache_credentials.is_empty() { None } else { Some(&self.cache_credentials) }; let files_cache = if self.cache_files.is_empty() { None } else { Some(&self.cache_files) }; let max_size = if self.cache_max_size != 0 { Some(self.cache_max_size) } else { None }; let cache = Cache::new(credentials_cache, None, files_cache, max_size)?; if let Some(cached_cred) = cache.credentials() { if !self.username.is_empty() && self.username != cached_cred.username { gst::debug!( cat, obj: &src, "ignore cached credentials for user {} which mismatch user {}", cached_cred.username, self.username ); } else { gst::debug!( cat, obj: &src, "reuse cached credentials for user {}", cached_cred.username ); if let Ok((session, _credentials)) = Session::connect( SessionConfig::default(), cached_cred, Some(cache.clone()), true, ) .await { return Ok(session); } } } gst::debug!( cat, obj: &src, "credentials not in cache or cached credentials invalid", ); if self.username.is_empty() { bail!("username is not set and credentials are not in cache"); } if self.password.is_empty() { bail!("password is not set and credentials are not in cache"); } let cred = Credentials::with_password(&self.username, &self.password); let (session, _credentials) = Session::connect(SessionConfig::default(), cred, Some(cache), true).await?; Ok(session) } pub fn track_id(&self) -> anyhow::Result { if self.track.is_empty() { bail!("track is not set"); } let track = SpotifyId::from_uri(&self.track).map_err(|_| { anyhow::anyhow!("failed to create Spotify URI from track {}", self.track) })?; Ok(track) } }