use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, Utc};
use dotenv;
use json::JsonValue;
use std::fmt::{self, Debug};
use std::fs;
use std::sync::RwLock;
use crate::authorization::{
generate_verifier, get_access_token, get_authorization_code, refresh_access_token,
};
pub trait SpotifyObject {
fn new(raw_object: &JsonValue) -> Self; }
pub struct SpotifyImage {
pub url: String,
pub height: i32,
pub width: i32,
}
impl Debug for SpotifyImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SpotifyImage")
.field("url", &self.url)
.field("height", &self.height)
.field("width", &self.width)
.finish()
}
}
pub enum AlbumType {
Album,
Single,
Compilation,
}
pub enum RestrictionReason {
Market,
Product,
Explicit,
None,
}
pub enum ReleaseDatePrecision {
Year,
Month,
Day,
None,
}
pub struct ExternalTrackIds {
pub isrc: Option<String>,
pub ean: Option<String>,
pub upc: Option<String>,
}
pub enum SpotifyContext {
Album(String),
Playlist(String),
Artist(String),
}
impl SpotifyContext {
pub fn uri(&self) -> String {
match self {
SpotifyContext::Album(id) => format!("spotify:album:{}", id),
SpotifyContext::Playlist(id) => format!("spotify:playlist:{}", id),
SpotifyContext::Artist(id) => format!("spotify:artist:{}", id),
}
}
pub fn new(context: &JsonValue) -> Option<SpotifyContext> {
let context_type = match context["type"].as_str() {
Some(t) => t,
None => return None, };
let context_uri = match context["uri"].as_str() {
Some(uri) => uri,
None => return None, };
let context_id = context_uri.split(':').last().unwrap();
match context_type {
"album" => Some(SpotifyContext::Album(context_id.to_string())),
"playlist" => Some(SpotifyContext::Playlist(context_id.to_string())),
"artist" => Some(SpotifyContext::Artist(context_id.to_string())),
_ => None,
}
}
}
impl Debug for SpotifyContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpotifyContext::Album(id) => f
.debug_struct("SpotifyContext")
.field("type", &"album")
.field("id", &id)
.finish(),
SpotifyContext::Playlist(id) => f
.debug_struct("SpotifyContext")
.field("type", &"playlist")
.field("id", &id)
.finish(),
SpotifyContext::Artist(id) => f
.debug_struct("SpotifyContext")
.field("type", &"artist")
.field("id", &id)
.finish(),
}
}
}
pub struct SpotifyCollection<T: SpotifyObject + Debug> {
pub href: String,
pub items: Vec<T>,
pub limit: i32,
pub next: Option<String>,
pub offset: i32,
pub previous: Option<String>,
pub total: i32,
}
impl<T: SpotifyObject + Debug> fmt::Debug for SpotifyCollection<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SpotifyCollection")
.field("items", &self.items)
.field("limit", &self.limit)
.field("next", &self.next)
.field("offset", &self.offset)
.field("previous", &self.previous)
.field("total", &self.total)
.finish()
}
}
pub struct Category {
pub href: String,
pub icons: Vec<SpotifyImage>,
pub id: String,
pub name: String,
}
impl fmt::Debug for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Category")
.field("id", &self.id)
.field("name", &self.name)
.finish()
}
}
pub struct Album {
pub album_type: AlbumType, pub total_tracks: i32, pub available_markets: Vec<String>, pub spotify_url: String, pub href: String, pub id: String, pub images: Vec<SpotifyImage>, pub name: String, pub release_date: Option<NaiveDate>, pub release_date_precision: ReleaseDatePrecision, pub restriction_reason: RestrictionReason, pub uri: String, pub artists: Option<Vec<Artist>>, pub tracks: Option<SpotifyCollection<Track>>, }
impl fmt::Debug for Album {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Album")
.field("name", &self.name)
.field("total_tracks", &self.total_tracks)
.field("id", &self.id)
.field("release_date", &self.release_date)
.field("artists", &self.artists)
.field("tracks", &self.tracks)
.finish()
}
}
pub struct DatedAlbum {
pub album: Album, pub date_added: Option<NaiveDateTime>, }
impl fmt::Debug for DatedAlbum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DatedAlbum")
.field("album", &self.album)
.field("date_added", &self.date_added)
.finish()
}
}
pub struct Artist {
pub spotify_url: String, pub total_followers: i32, pub genres: Vec<String>, pub href: String, pub id: String, pub images: Vec<SpotifyImage>, pub name: String, pub popularity: i32, pub uri: String, }
impl fmt::Debug for Artist {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Artist")
.field("id", &self.id)
.field("name", &self.name)
.finish()
}
}
pub struct Track {
pub album: Option<Album>, pub artists: Option<Vec<Artist>>, pub available_markets: Vec<String>, pub disc_number: i32, pub duration: i32, pub explicit: bool, pub external_ids: ExternalTrackIds, pub spotify_url: String, pub href: String, pub id: String, pub restriction_reason: RestrictionReason, pub name: String, pub popularity: i32, pub preview_url: Option<String>, pub track_number: i32, pub uri: String, pub is_local: bool,
}
impl fmt::Debug for Track {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Track")
.field("id", &self.id)
.field("name", &self.name)
.field("artists", &self.artists)
.finish()
}
}
pub struct DatedTrack {
pub track: Track, pub date_added: Option<NaiveDateTime>, }
impl fmt::Debug for DatedTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DatedTrack")
.field("track", &self.track)
.field("date_added", &self.date_added)
.finish()
}
}
pub struct FeatureTrack {
pub acousticness: f64, pub analysis_url: String, pub danceability: f64, pub duration: i32, pub energy: f64, pub id: String, pub instrumentalness: f64, pub key: i32, pub liveness: f64, pub loudness: f64, pub mode: i32, pub speechiness: f64, pub tempo: f64, pub time_signature: i32, pub track_href: String, pub uri: String, pub valence: f64, }
impl fmt::Debug for FeatureTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FeatureTrack")
.field("acousticness", &self.acousticness)
.field("danceability", &self.danceability)
.field("duration", &self.duration)
.field("energy", &self.energy)
.field("id", &self.id)
.field("instrumentalness", &self.instrumentalness)
.field("key", &self.key)
.field("liveness", &self.liveness)
.field("loudness", &self.loudness)
.field("mode", &self.mode)
.field("speechiness", &self.speechiness)
.field("tempo", &self.tempo)
.field("time_signature", &self.time_signature)
.field("valence", &self.valence)
.finish()
}
}
pub struct Bar {
pub start: f64, pub duration: f64, pub confidence: f64, }
impl fmt::Debug for Bar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bar")
.field("start", &self.start)
.field("duration", &self.duration)
.finish()
}
}
pub struct Beat {
pub start: f64, pub duration: f64, pub confidence: f64, }
impl fmt::Debug for Beat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Beat")
.field("start", &self.start)
.field("duration", &self.duration)
.finish()
}
}
pub struct Section {
pub start: f64, pub duration: f64, pub confidence: f64, pub loudness: f64, pub tempo: f64, pub tempo_confidence: f64, pub key: i32, pub key_confidence: f64, pub mode: i32, pub mode_confidence: f64, pub time_signature: i32, pub time_signature_confidence: f64, }
impl fmt::Debug for Section {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Section")
.field("start", &self.start)
.field("duration", &self.duration)
.field("loudness", &self.loudness)
.field("tempo", &self.tempo)
.field("key", &self.key)
.field("mode", &self.mode)
.field("time_signature", &self.time_signature)
.finish()
}
}
pub struct Segment {
pub start: f64, pub duration: f64, pub confidence: f64, pub loudness_start: f64, pub loudness_max: f64, pub loudness_max_time: f64, pub loudness_end: f64,
pub pitches: Vec<f64>, pub timbre: Vec<f64>, }
impl fmt::Debug for Segment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Segment")
.field("start", &self.start)
.field("duration", &self.duration)
.field("loudness_start", &self.loudness_start)
.field("loudness_max", &self.loudness_max)
.field("loudness_max_time", &self.loudness_max_time)
.field("loudness_end", &self.loudness_end)
.field("pitches", &self.pitches)
.field("timbre", &self.timbre)
.finish()
}
}
pub struct Tatum {
pub start: f64, pub duration: f64, pub confidence: f64, }
impl fmt::Debug for Tatum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tatum")
.field("start", &self.start)
.field("duration", &self.duration)
.finish()
}
}
pub struct AnalysisTrack {
pub analyzer_version: String, pub platform: String, pub detailed_status: String, pub timestamp: i64, pub analysis_time: f64, pub input_process: String,
pub num_samples: i32, pub duration: f64, pub analysis_sample_rate: i32, pub analysis_channels: i32, pub end_fade_in: f64, pub start_fade_out: f64, pub loudness: f64, pub tempo: f64, pub tempo_confidence: f64, pub time_signature: i32, pub time_signature_confidence: f64, pub key: i32, pub key_confidence: f64, pub mode: i32, pub mode_confidence: f64, pub code_string: String, pub code_version: String, pub echoprint_string: String, pub echoprint_version: String, pub synch_string: String, pub synch_version: String, pub rhythm_string: String, pub rhythm_version: String,
pub bars: Vec<Bar>, pub beats: Vec<Beat>, pub sections: Vec<Section>, pub segments: Vec<Segment>, pub tatums: Vec<Tatum>, }
impl fmt::Debug for AnalysisTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AnalysisTrack")
.field("duration", &self.duration)
.field("end_of_fade_in", &self.end_fade_in)
.field("start_fade_out", &self.start_fade_out)
.field("loudness", &self.loudness)
.field("tempo", &self.tempo)
.field("time_signature", &self.time_signature)
.field("key", &self.key)
.field("mode", &self.mode)
.field("bars", &self.bars)
.field("beats", &self.beats)
.field("sections", &self.sections)
.field("segments", &self.segments)
.field("tatums", &self.tatums)
.finish()
}
}
pub struct User {
pub country: Option<String>, pub display_name: Option<String>, pub spotify_url: String, pub total_followers: i32, pub href: String, pub id: String, pub images: Vec<SpotifyImage>, pub product: Option<String>, pub uri: String, }
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("User")
.field("display_name", &self.display_name)
.field("id", &self.id)
.field("total_followers", &self.total_followers)
.finish()
}
}
pub struct Playlist {
pub collaborative: bool, pub description: Option<String>, pub spotify_url: String, pub total_followers: i32, pub href: String, pub id: String, pub images: Vec<SpotifyImage>, pub name: String, pub owner: User, pub public: Option<bool>, pub snapshot_id: String, pub tracks: Option<SpotifyCollection<PlaylistTrack>>, pub uri: String, }
impl fmt::Debug for Playlist {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Playlist")
.field("name", &self.name)
.field("description", &self.description)
.field("id", &self.id)
.field("total_followers", &self.total_followers)
.field("owner", &self.owner)
.field("tracks", &self.tracks)
.finish()
}
}
pub struct PlaylistTrack {
pub added_at: Option<NaiveDateTime>, pub added_by: User, pub is_local: bool, pub track: Track, }
impl fmt::Debug for PlaylistTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PlaylistTrack")
.field("added_at", &self.added_at)
.field("added_by", &self.added_by)
.field("track", &self.track)
.finish()
}
}
pub struct PlayedTrack {
pub track: Track, pub played_at: Option<NaiveDateTime>, pub context: Option<SpotifyContext>, }
impl fmt::Debug for PlayedTrack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PlayedTrack")
.field("track", &self.track)
.field("played_at", &self.played_at)
.field("context", &self.context)
.finish()
}
}
pub enum TimeRange {
ShortTerm,
MediumTerm,
LongTerm,
}
impl fmt::Debug for TimeRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimeRange::ShortTerm => write!(f, "ShortTerm"),
TimeRange::MediumTerm => write!(f, "MediumTerm"),
TimeRange::LongTerm => write!(f, "LongTerm"),
}
}
}
pub enum RepeatState {
Track, Context, Off, }
impl fmt::Debug for RepeatState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RepeatState::Track => write!(f, "Track"),
RepeatState::Context => write!(f, "Context"),
RepeatState::Off => write!(f, "Off"),
}
}
}
impl RepeatState {
pub fn to_string(&self) -> String {
match self {
RepeatState::Track => String::from("track"),
RepeatState::Context => String::from("context"),
RepeatState::Off => String::from("off"),
}
}
}
pub struct Device {
pub id: String, pub is_active: bool, pub is_private_session: bool, pub is_restricted: bool, pub name: String, pub device_type: String, pub volume_percent: Option<i32>, }
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Device")
.field("id", &self.id)
.field("is active", &self.is_active)
.field("name", &self.name)
.field("device_type", &self.device_type)
.field("volume_percent", &self.volume_percent)
.finish()
}
}
pub struct PlaybackActions {
pub interrupting_playback: bool, pub pausing: bool, pub resuming: bool, pub seeking: bool, pub skipping_next: bool, pub skipping_prev: bool, pub toggling_repeat_context: bool, pub toggling_repeat_track: bool, pub toggling_shuffle: bool, pub transferring_playback: bool, }
impl fmt::Debug for PlaybackActions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PlaybackActions")
.field("interrupting_playback", &self.interrupting_playback)
.field("pausing", &self.pausing)
.field("resuming", &self.resuming)
.field("seeking", &self.seeking)
.field("skipping_next", &self.skipping_next)
.field("skipping_prev", &self.skipping_prev)
.field("toggling_repeat_context", &self.toggling_repeat_context)
.field("toggling_repeat_track", &self.toggling_repeat_track)
.field("toggling_shuffle", &self.toggling_shuffle)
.field("transferring_playback", &self.transferring_playback)
.finish()
}
}
pub struct Playback {
pub device: Option<Device>, pub repeat_state: RepeatState, pub shuffle_state: bool, pub timestamp: Option<NaiveDateTime>, pub progress: Option<i32>, pub is_playing: bool, pub track: Option<Track>, pub actions: Option<PlaybackActions>, }
impl fmt::Debug for Playback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Playback")
.field("device", &self.device)
.field("repeat_state", &self.repeat_state)
.field("shuffle_state", &self.shuffle_state)
.field("timestamp", &self.timestamp)
.field("progress", &self.progress)
.field("is_playing", &self.is_playing)
.field("track", &self.track)
.finish()
}
}
pub enum SpotifyError {
RequestError(String),
InsufficientScope(String),
FailedRequest(String),
BadOrExpiredToken(String),
RateLimitExceeded(String),
BadRequest(String),
InvalidRequest(String),
AuthenticationError(String),
NotAuthenticated,
FileError(String),
NoFile,
GeneralError(String),
Unauthorized(String),
}
impl fmt::Debug for SpotifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpotifyError::RequestError(e) => write!(f, "Request error: {}", e),
SpotifyError::InsufficientScope(scopes) => {
write!(f, "Insufficient scope. Need: {:?}", scopes)
}
SpotifyError::FailedRequest(e) => write!(f, "Failed request: {}", e),
SpotifyError::BadOrExpiredToken(e) => write!(f, "Bad or expired token: {}", e),
SpotifyError::RateLimitExceeded(e) => write!(f, "Rate limited: {}", e),
SpotifyError::BadRequest(e) => write!(f, "Bad request: {}", e),
SpotifyError::InvalidRequest(e) => write!(f, "Invalid request: {}", e),
SpotifyError::AuthenticationError(e) => write!(f, "Authentication error: {}", e),
SpotifyError::NotAuthenticated => write!(f, "Not authenticated"),
SpotifyError::FileError(e) => write!(f, "File error: {}", e),
SpotifyError::NoFile => write!(f, "No file present"),
SpotifyError::GeneralError(e) => write!(f, "General error: {}", e),
SpotifyError::Unauthorized(e) => write!(f, "Unauthorized: {}", e),
}
}
}
pub struct Spotify {
client_id: RwLock<Option<String>>,
scope: RwLock<Option<String>>,
access_token: RwLock<Option<String>>,
refresh_token: RwLock<Option<String>>,
expires_at: RwLock<Option<DateTime<Utc>>>,
}
impl Default for Spotify {
fn default() -> Self {
Spotify::new()
}
}
impl Spotify {
pub fn new() -> Spotify {
Spotify {
client_id: RwLock::new(None),
scope: RwLock::new(None),
access_token: RwLock::new(None),
refresh_token: RwLock::new(None),
expires_at: RwLock::new(None),
}
}
pub fn new_from_auth_code(
authorization_code: &str,
client_id: &str,
scope: String,
code_verifier: &str,
redirect_uri: &str,
) -> Spotify {
let (access_token, refresh_token, expires_in) =
get_access_token(authorization_code, client_id, code_verifier, redirect_uri).unwrap();
let expires_at = Utc::now() + Duration::seconds(expires_in);
Spotify {
client_id: RwLock::new(Some(String::from(client_id))),
scope: RwLock::new(Some(scope)),
access_token: RwLock::new(Some(access_token)),
refresh_token: RwLock::new(Some(refresh_token)),
expires_at: RwLock::new(Some(expires_at)),
}
}
pub fn authenticate(&self, localhost_port: String, scope: String) -> Result<(), SpotifyError> {
let client_id = dotenv::var("CLIENT_ID").unwrap();
let (code_verifier, code_challenge) = generate_verifier();
let redirect_uri = format!("http://localhost:{}/callback", &localhost_port);
let auth_code_result = get_authorization_code(
&client_id,
&localhost_port,
&redirect_uri,
&scope,
&code_challenge,
);
let (access_token, refresh_token, expires_in) = match auth_code_result {
Ok(auth_code) => {
get_access_token(&auth_code, &client_id, &code_verifier, &redirect_uri).unwrap()
}
Err(e) => return Err(SpotifyError::AuthenticationError(e.to_string())),
};
let expires_at = Utc::now() + Duration::seconds(expires_in);
let mut self_client_id = self.client_id.write().unwrap();
*self_client_id = Some(client_id);
let mut self_scope = self.scope.write().unwrap();
*self_scope = Some(scope);
let mut self_access_token = self.access_token.write().unwrap();
*self_access_token = Some(access_token);
let mut self_refresh_token = self.refresh_token.write().unwrap();
*self_refresh_token = Some(refresh_token);
let mut self_expires_at = self.expires_at.write().unwrap();
*self_expires_at = Some(expires_at);
Ok(())
}
pub fn check_scope(&self, scope: &str) -> Result<(), SpotifyError> {
let current_scope = &*self.scope.read().unwrap(); let scopes: Vec<&str> = match current_scope {
Some(scope) => scope.split_whitespace().collect(), None => Vec::new(), };
let required_scopes: Vec<&str> = scope.split_whitespace().collect();
let missing_scopes: Vec<&str> = required_scopes
.iter()
.copied()
.filter(|s| !scopes.contains(s))
.collect();
if missing_scopes.len() > 0 {
return Err(SpotifyError::InsufficientScope(missing_scopes.join(" ")));
}
Ok(())
}
pub fn access_token(&self) -> Result<String, SpotifyError> {
if self.access_token.read().unwrap().is_none() {
return Err(SpotifyError::NotAuthenticated);
};
match *self.expires_at.read().unwrap() {
Some(expires_at) => {
if Utc::now() > expires_at {
let (access_token, expires_at, refresh_token) = self.refresh()?;
let mut self_access_token = self.access_token.write().unwrap();
*self_access_token = Some(access_token);
let mut self_expires_at = self.expires_at.write().unwrap();
*self_expires_at = Some(expires_at);
let mut self_refresh_token = self.refresh_token.write().unwrap();
*self_refresh_token = Some(refresh_token);
}
return Ok((*self.access_token.read().unwrap())
.as_ref()
.unwrap()
.to_string()); }
None => return Err(SpotifyError::NotAuthenticated),
};
}
fn refresh(&self) -> Result<(String, DateTime<Utc>, String), SpotifyError> {
if self.refresh_token.read().unwrap().is_none() || self.client_id.read().unwrap().is_none()
{
return Err(SpotifyError::NotAuthenticated);
}
let (access_token, expires_in, refresh_token) = match refresh_access_token(
&self.refresh_token.read().unwrap().as_ref().unwrap(),
&self.client_id.read().unwrap().as_ref().unwrap(),
) {
Ok((access_token, expires_in, refresh_token)) => {
(access_token, expires_in, refresh_token)
}
Err(e) => panic!("{:?}", e), };
let expires_at = Utc::now() + Duration::seconds(expires_in);
Ok((access_token, expires_at, refresh_token))
}
pub fn save_to_file(&self, file_name: &str) -> Result<(), SpotifyError> {
if self.client_id.read().unwrap().is_none()
|| self.scope.read().unwrap().is_none()
|| self.access_token.read().unwrap().is_none()
|| self.refresh_token.read().unwrap().is_none()
|| self.expires_at.read().unwrap().is_none()
{
return Err(SpotifyError::NotAuthenticated);
}
let data = format!(
"{}\n{}\n{}",
self.client_id.read().unwrap().as_ref().unwrap(),
self.scope.read().unwrap().as_ref().unwrap(),
self.refresh_token.read().unwrap().as_ref().unwrap()
);
match fs::write(file_name, data) {
Ok(_) => Ok(()),
Err(e) => Err(SpotifyError::FileError(e.to_string())),
}
}
pub fn new_from_file(file_name: &str) -> Result<Spotify, SpotifyError> {
let data = match fs::read_to_string(file_name) {
Ok(data) => data,
Err(_) => return Err(SpotifyError::NoFile), };
let mut lines = data.lines();
let client_id = lines.next().unwrap().to_string(); let scope = lines.next().unwrap().to_string(); let refresh_token = lines.next().unwrap().to_string();
let (access_token, expires_in, new_refresh_token) =
refresh_access_token(&refresh_token, &client_id)?; let expires_at = Utc::now() + Duration::seconds(expires_in);
Ok(Spotify {
client_id: RwLock::new(Some(client_id)),
scope: RwLock::new(Some(scope)),
access_token: RwLock::new(Some(access_token)),
refresh_token: RwLock::new(Some(new_refresh_token)),
expires_at: RwLock::new(Some(expires_at)),
})
}
pub fn authenticate_from_file(&self, file_name: &str) -> Result<(), SpotifyError> {
let data = match fs::read_to_string(file_name) {
Ok(data) => data,
Err(_) => return Err(SpotifyError::NoFile), };
let mut lines = data.lines();
let client_id = lines.next().unwrap().to_string(); let scope = lines.next().unwrap().to_string(); let refresh_token = lines.next().unwrap().to_string();
let (access_token, expires_in, new_refresh_token) =
refresh_access_token(&refresh_token, &client_id)?; let expires_at = Utc::now() + Duration::seconds(expires_in);
let mut self_client_id = self.client_id.write().unwrap();
*self_client_id = Some(client_id);
let mut self_scope = self.scope.write().unwrap();
*self_scope = Some(scope);
let mut self_access_token = self.access_token.write().unwrap();
*self_access_token = Some(access_token);
let mut self_refresh_token = self.refresh_token.write().unwrap();
*self_refresh_token = Some(new_refresh_token);
let mut self_expires_at = self.expires_at.write().unwrap();
*self_expires_at = Some(expires_at);
Ok(())
}
pub fn is_authenticated(&self) -> bool {
match self.access_token.read() {
Ok(access_token) => access_token.is_some(),
Err(_) => false,
}
}
}