use crate::database; use crate::errors::Error; use lazy_static::lazy_static; use rspotify::client::{ApiError, Spotify}; use rspotify::model::page::Page; use rspotify::model::playlist::*; use rspotify::model::track::*; use rspotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; use rspotify::senum::TimeRange; use rspotify::util::process_token; use std::collections::HashMap; use std::sync::{Arc, Mutex}; lazy_static! { static ref CACHE: Arc>> = Arc::new(Mutex::new(HashMap::new())); } static CHUNK_SIZE: u32 = 50; macro_rules! get_items { ($t: ty, $index:ident, $spotify_call:expr) => {{ $index = 0; let mut result: Vec<$t> = Vec::new(); loop { let res: Result, failure::Error> = $spotify_call.await; match res { Ok(mut page) => { $index += CHUNK_SIZE; if page.items.is_empty() { break; } result.append(&mut page.items); } Err(e) => match e.downcast::() { Ok(ApiError::RateLimited(x)) => { std::thread::sleep(std::time::Duration::from_secs(x.unwrap_or(5) as u64)) } cause => { println!("Error: {:?}", cause); break; } }, } } result }}; } pub async fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result<(), Error> { let mut index; let playlists = get_items!( SimplifiedPlaylist, index, spotify.current_user_playlists(CHUNK_SIZE, index) ); let library = get_items!( SavedTrack, index, spotify.current_user_saved_tracks(CHUNK_SIZE, index) ); let top_tracks = get_items!( FullTrack, index, spotify.current_user_top_tracks(CHUNK_SIZE, index, TimeRange::MediumTerm) ); for track in library { if let Err(e) = database::insert_track(db_uid, track.track, 5) { println!("failed to load track to db: {:?}", e) }; } for (pos, track) in top_tracks.iter().enumerate() { let weight = ((50.0 - pos as f64) / 50.0 * 10.0).floor() as i32; if let Err(e) = database::insert_track(db_uid, track.clone(), 5 + weight) { println!("failed to load track to db: {:?}", e) }; } for playlist in playlists { let tracks = get_items!( PlaylistTrack, index, spotify.user_playlist_tracks( spotify_uid.as_ref(), &playlist.id, None, CHUNK_SIZE, index, None, ) ); for track in tracks.iter().map(|x| x.track.clone()).flatten() { if let Err(e) = database::insert_track(db_uid, track, 1) { println!("failed to load track to db: {:?}", e) }; } } Ok(()) } pub async fn auth_user(name: &str, url: String) -> Result<(String, Spotify), Error> { let mut oauth = { let mut guard = CACHE.lock()?; guard.remove(name)? }; println!("auth: {:?} url: {}", oauth, url); let mut token_string = format!("?code={}", url); let token_info = process_token(&mut oauth, &mut token_string); let client_credential = SpotifyClientCredentials::default() .token_info(token_info.await?) .build(); let spotify = Spotify::default() .client_credentials_manager(client_credential) .build(); let user_id = spotify .current_user() .await .map_err(|e| format!("failed to load currentuser {:?}", e))? .id; Ok((user_id, spotify)) } pub fn token(name: String) -> Result { let state = rspotify::util::generate_random_string(16); let oauth = SpotifyOAuth::default(); let oauth = oauth .scope("playlist-read-private, playlist-read-collaborative, user-read-private, user-follow-read, user-library-read") .build(); let auth_url = oauth.get_authorize_url(Some(&state), None); let mut guard = CACHE.lock()?; guard.insert(name, oauth); Ok(auth_url) }