diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/database.rs | 58 | ||||
-rw-r--r-- | src/serve.rs | 204 | ||||
-rw-r--r-- | src/spotify.rs | 172 |
3 files changed, 203 insertions, 231 deletions
diff --git a/src/database.rs b/src/database.rs index 326c1e0..1a3a415 100644 --- a/src/database.rs +++ b/src/database.rs @@ -3,10 +3,10 @@ use postgres::{Client, NoTls}; use std::sync::{Arc, Mutex}; lazy_static! { - static ref CLIENT: Arc<Mutex<Client>> = Arc::new(Mutex::new(Client::connect( - "host=track_db user=postgres password=example", - NoTls - )?)); + static ref CLIENT: Arc<Mutex<Client>> = Arc::new(Mutex::new( + Client::connect("host=track_db user=postgres password=example", NoTls) + .expect("failed to connect to database") + )); } fn initialize_db() -> Result<(), Error> { @@ -45,8 +45,8 @@ fn initialize_db() -> Result<(), Error> { } use rspotify::spotify::model::track::FullTrack; -fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> { - let mut client = crate::CLIENT.lock()?; +pub fn insert_track(user_id: i32, track: FullTrack, weight: i32) -> Result<(), Error> { + let mut client = CLIENT.lock()?; if track.id.is_none() { println!("{:#?}", track); @@ -56,8 +56,7 @@ fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> { client.execute( "INSERT INTO track (track_code, name, artist, popularity) VALUES ($1, $2, $3, $4) - ON CONFLICT DO NOTHING - ", + ON CONFLICT DO NOTHING", &[ &(track.id.clone()?), &track.name, @@ -71,37 +70,32 @@ fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> { )?[0] .get(0); println!("uid: {} tid: {}", user_id, track_id); - client.batch_execute( + 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], )?; + client.execute( + "UPDATE user_track SET count = count + $3 WHERE track_id = $1 AND user_id = $2;", + &[&track_id, &user_id, &weight], + )?; Ok(()) } -fn insert_user(name: &str) -> Result<(i32, String), Error> { - let mut client = crate::CLIENT.lock()?; - client.batch_execute( - " - INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING; - DELETE FROM user_track WHERE user_name = $1; - ", - &[&user_id], +pub fn insert_user(name: &str) -> Result<i32, Error> { + let mut client = CLIENT.lock()?; + client.execute( + "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;", + &[&name], )?; - let user_id: i32 = client - .query_one( - "SELECT user_id FROM suser WHERE user_name = $1;", - &[&user_id], - )? - .get(0); - //reset user_track relation - Ok((user_id, name)) + let db_user_id = get_uid(name, &mut *client)?; + client.execute("DELETE FROM user_track WHERE user_id = $1;", &[&db_user_id])?; + Ok(db_user_id) } fn get_uid(name: &str, client: &mut postgres::Client) -> Result<i32, Error> { @@ -111,13 +105,8 @@ fn get_uid(name: &str, client: &mut postgres::Client) -> Result<i32, Error> { Ok(x) } -lazy_static! { - static ref CACHE: Arc<Mutex<HashMap<String, SpotifyOAuth>>> = - Arc::new(Mutex::new(HashMap::new())); -} - pub fn match_users(name1: String, name2: String) -> Result<String, Error> { - let mut client = crate::CLIENT.lock()?; + let mut client = CLIENT.lock()?; let mut songs = String::new(); for row in client.query( " @@ -132,7 +121,7 @@ pub fn match_users(name1: String, name2: String) -> Result<String, Error> { SELECT track_id, name, artist FROM track JOIN ( - SELECT track_id + SELECT track_id FROM user_track JOIN suser USING (user_id) JOIN track USING (track_id) @@ -152,9 +141,8 @@ pub fn match_users(name1: String, name2: String) -> Result<String, Error> { Ok(songs) } -#[get("/user")] pub fn get_users() -> Result<String, Error> { - let mut client = crate::CLIENT.lock()?; + let mut client = CLIENT.lock()?; let mut users = String::new(); for row in client.query("SELECT user_name FROM suser", &[])? { let user: String = row.get(0); diff --git a/src/serve.rs b/src/serve.rs index 7014641..53a3cac 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -1,214 +1,26 @@ +use crate::database; use crate::errors::Error; -use lazy_static::lazy_static; +use crate::spotify; 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<Mutex<HashMap<String, SpotifyOAuth>>> = - Arc::new(Mutex::new(HashMap::new())); -} #[get("/callback/<name>/<url>")] 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::<ApiError>() { - 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::<ApiError>() { - 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(()) + let (spotify_uid, spotify_client) = spotify::auth_user(name.as_ref(), url)?; + let uid = database::insert_user(spotify_uid.as_ref())?; + spotify::load_profile(uid, spotify_uid.as_ref(), spotify_client) } #[get("/token/<name>")] pub fn token(name: String) -> Result<Redirect, Error> { - 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<i32, Error> { - let x: i32 = client - .query_one("SELECT user_id FROM suser where user_name = $1;", &[&name])? - .get(0); - Ok(x) + Ok(Redirect::to(spotify::token(name)?)) } #[get("/match/<name1>/<name2>")] pub fn match_users(name1: String, name2: String) -> Result<String, Error> { - 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) + database::match_users(name1, name2) } #[get("/user")] pub fn get_users() -> Result<String, Error> { - 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) + database::get_users() } diff --git a/src/spotify.rs b/src/spotify.rs new file mode 100644 index 0000000..b9a1147 --- /dev/null +++ b/src/spotify.rs @@ -0,0 +1,172 @@ +use crate::database; +use crate::errors::Error; +use lazy_static::lazy_static; +use rspotify::spotify::client::{ApiError, Spotify}; +use rspotify::spotify::model::playlist::FullPlaylist; +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<Mutex<HashMap<String, SpotifyOAuth>>> = + Arc::new(Mutex::new(HashMap::new())); +} + +static CHUNK_SIZE: i32 = 50; + +macro_rules! get_items { + ($func_name:ident, $spotify_call:stmt, $t: ty) => { + fn $func_name(spotify_uid: &str, spotify: &mut Spotify) -> Result<Vec<$t>, Error> { + let mut index = 0; + let mut result = Vec::new(); + loop { + match $spotify_call { + Ok(items) => { + index += CHUNK_SIZE; + if items.items.is_empty() { + break; + } + result.append(items.items); + } + Err(e) => match e.downcast::<ApiError>() { + Ok(ApiError::RateLimited(x)) => std::thread::sleep( + std::time::Duration::from_secs(x.unwrap_or(5) as u64), + ), + + cause => { + println!("Error: {:?}", cause); + break; + } + }, + } + } + Ok((result)) + } + }; +} + +get_items!( + playlists, + spotify.user_playlists(spotify_uid.as_ref(), Some(CHUNK_SIZE), Some(playlist_index)), + FullPlaylist +); +fn load_playlist(spotify_uid: &str, spotify: &mut Spotify) -> Result<Vec<FullPlaylist>, Error> { + 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 {} + } + Err(e) => match e.downcast::<ApiError>() { + Ok(ApiError::RateLimited(x)) => { + std::thread::sleep(std::time::Duration::from_secs(x.unwrap_or(5) as u64)) + } + + cause => { + println!("Error: {:?}", cause); + break; + } + }, + } + } + Ok(()) +} + +pub fn load_profile(db_uid: i32, spotify_uid: &str, spotify: Spotify) -> Result<(), Error> { + 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::<ApiError>() { + 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::<ApiError>() { + Ok(ApiError::RateLimited(x)) => { + std::thread::sleep(std::time::Duration::from_secs(x.unwrap_or(5) as u64)) + } + + cause => { + println!("Error: {:?}", cause); + break; + } + }, + } + } + Ok(()) +} + +pub fn auth_user(name: &str, url: String) -> Result<(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; + Ok((user_id, spotify)) +} + +#[get("/token/<name>")] +pub fn token(name: String) -> Result<String, Error> { + 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(auth_url) +} |