use crate::errors::Error; use lazy_static::lazy_static; use rocket::response::Redirect; use rspotify::spotify::client::{ApiError, Spotify}; use rspotify::spotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; use rspotify::spotify::util::process_token; use std::collections::HashMap; use std::sync::{Arc, Mutex}; lazy_static! { static ref CACHE: Arc>> = Arc::new(Mutex::new(HashMap::new())); } #[get("/callback//")] pub fn get_tracks(name: String, url: String) -> Result<(), Error> { let (uid, spotify_uid, spotify) = autenth_user(name.as_ref(), url)?; let chunk_size = 50; let mut playlist_index = 0; loop { match spotify.user_playlists(spotify_uid.as_ref(), Some(chunk_size), Some(playlist_index)) { Ok(playlists) => { playlist_index += chunk_size; if playlists.items.is_empty() { break; } for playlist in playlists.items { println!("playlist: {:?}", playlist.name); let mut track_index = 0; loop { match spotify.user_playlist_tracks( spotify_uid.as_ref(), &playlist.id, None, Some(chunk_size), Some(track_index), None, ) { Ok(tracks) => { track_index += chunk_size; if tracks.items.is_empty() { break; } for track in tracks.items { if let Err(e) = insert_track(uid, track.track) { println!("failed to load track to db: {:?}", e) }; } } 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; } }, } } } } 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; } }, } } Ok(()) } fn autenth_user(name: &str, url: String) -> Result<(i32, String, Spotify), Error> { let mut guard = CACHE.lock()?; let mut oauth = guard.remove(name)?; println!("auth: {:?} url: {}", oauth, url); let token_info = process_token(&mut oauth, &mut ("?code=".to_owned() + url.as_ref())); let client_credential = SpotifyClientCredentials::default() .token_info(token_info?) .build(); let spotify = Spotify::default() .client_credentials_manager(client_credential) .build(); let user_id = spotify .current_user() .map_err(|e| format!("failed to load currentuser {:?}", e))? .id; let mut client = crate::CLIENT.lock()?; client.execute( "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;", &[&user_id], )?; let uid = get_uid(user_id.as_ref(), &mut client)?; //reset user_track relation client.execute("DELETE FROM user_track WHERE user_id = $1;", &[&uid])?; Ok((uid, user_id, spotify)) } use rspotify::spotify::model::track::FullTrack; fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> { let mut client = crate::CLIENT.lock()?; if track.id.is_none() { println!("{:#?}", track); return Err("failed to load get track information".into()); } print!(" {} ", track.id.clone()?); client.execute( "INSERT INTO track (track_code, name, artist, popularity) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING ", &[ &(track.id.clone()?), &track.name, &track.artists[0].name, &(track.popularity as i32), ], )?; let track_id: i32 = client.query( "SELECT track_id FROM track where track_code = $1;", &[&(track.id?)], )?[0] .get(0); println!("uid: {} tid: {}", user_id, track_id); client.execute( " INSERT INTO user_track_raw (track_id, user_id, count) VALUES ($1, $2, $3) ON CONFLICT ON CONSTRAINT track_user_pkey DO NOTHING; UPDATE user_track SET count = count + 1 WHERE track_id = $1 AND user_id = $2; ", &[&track_id, &user_id, &0], )?; Ok(()) } #[get("/token/")] pub fn token(name: String) -> Result { let state = rspotify::spotify::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(Redirect::to(auth_url)) } fn get_uid(name: &str, client: &mut postgres::Client) -> Result { let x: i32 = client .query_one("SELECT user_id FROM suser where user_name = $1;", &[&name])? .get(0); Ok(x) } #[get("/match//")] pub fn match_users(name1: String, name2: String) -> Result { let mut client = crate::CLIENT.lock()?; let mut songs = String::new(); for row in client.query( " WITH users AS ( SELECT * FROM ( VALUES ($1), ($2) ) AS _ (user_id) ) SELECT track_id, name, artist FROM track JOIN ( SELECT track_id FROM user_track JOIN suser USING (user_id) JOIN track USING (track_id) WHERE suser.user_name IN (SELECT * FROM users) GROUP BY track_id HAVING COUNT(track_id) = (SELECT COUNT(*) FROM users) ORDER BY SUM(score) DESC ) AS _ USING (track_id) ; ", &[&name1.as_str(), &name2.as_str()], )? { let name: String = row.get(1); let artist: String = row.get(2); songs = format!("{}{} by {}\n", songs, name, artist); } Ok(songs) } #[get("/user")] pub fn get_users() -> Result { let mut client = crate::CLIENT.lock()?; let mut users = String::new(); for row in client.query("SELECT user_name FROM suser", &[])? { let user: String = row.get(0); users = format!("{}{}\n", users, user); } Ok(users) }