summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/database.rs58
-rw-r--r--src/serve.rs204
-rw-r--r--src/spotify.rs172
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)
+}