Implemented last fm endpoints

This commit is contained in:
Antoine Gersant 2018-10-28 17:05:14 -07:00
parent 4af2c0f09e
commit c25dc8155f
2 changed files with 98 additions and 20 deletions

View file

@ -249,7 +249,7 @@ fn get_endpoints(db: &Arc<DB>, index_channel: &Arc<index::CommandSender>) -> Mou
lastfm_router.get( lastfm_router.get(
"/scrobble", "/scrobble",
move |request: &mut Request| self::lastfm_scrobble(request, scrobble_db.deref()), move |request: &mut Request| self::lastfm_scrobble(request, scrobble_db.deref()),
"auth", "scrobble",
); );
auth_api_mount.mount("/lastfm/", lastfm_router); auth_api_mount.mount("/lastfm/", lastfm_router);

View file

@ -1,16 +1,19 @@
use rocket::http::{Cookie, Cookies, RawStr, Status}; use rocket::http::{Cookie, Cookies, RawStr, Status};
use rocket::request::{self, FromParam, FromRequest, Request}; use rocket::request::{self, FromParam, FromRequest, Request};
use rocket::response::content::Html;
use rocket::{Outcome, State}; use rocket::{Outcome, State};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use std::fs::File; use std::fs::File;
use std::path::PathBuf;
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf;
use std::str;
use std::sync::Arc; use std::sync::Arc;
use config::{self, Config, Preferences}; use config::{self, Config, Preferences};
use db::DB; use db::DB;
use errors; use errors;
use index; use index;
use lastfm;
use playlist; use playlist;
use serve; use serve;
use thumbnails; use thumbnails;
@ -45,6 +48,10 @@ pub fn get_routes() -> Vec<rocket::Route> {
save_playlist, save_playlist,
read_playlist, read_playlist,
delete_playlist, delete_playlist,
lastfm_link,
lastfm_unlink,
lastfm_now_playing,
lastfm_scrobble,
] ]
} }
@ -97,18 +104,18 @@ struct VFSPathBuf {
impl<'r> FromParam<'r> for VFSPathBuf { impl<'r> FromParam<'r> for VFSPathBuf {
type Error = &'r RawStr; type Error = &'r RawStr;
fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
let decoded_path = param.percent_decode_lossy(); let decoded_path = param.percent_decode_lossy();
Ok(VFSPathBuf{ Ok(VFSPathBuf {
path_buf: PathBuf::from(decoded_path.into_owned()) path_buf: PathBuf::from(decoded_path.into_owned()),
}) })
} }
} }
impl From<VFSPathBuf> for PathBuf { impl From<VFSPathBuf> for PathBuf {
fn from(vfs_path_buf: VFSPathBuf) -> Self { fn from(vfs_path_buf: VFSPathBuf) -> Self {
vfs_path_buf.path_buf.clone() vfs_path_buf.path_buf.clone()
} }
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -198,10 +205,11 @@ fn auth(
mut cookies: Cookies, mut cookies: Cookies,
) -> Result<Json<AuthOutput>, errors::Error> { ) -> Result<Json<AuthOutput>, errors::Error> {
user::auth::<DB>(&db, &credentials.username, &credentials.password)?; user::auth::<DB>(&db, &credentials.username, &credentials.password)?;
cookies.add_private(Cookie::new( cookies.add_private(
SESSION_FIELD_USERNAME, Cookie::build(SESSION_FIELD_USERNAME, credentials.username.clone())
credentials.username.clone(), .same_site(rocket::http::SameSite::Lax)
)); .finish(),
);
let auth_output = AuthOutput { let auth_output = AuthOutput {
admin: user::is_admin::<DB>(&db, &credentials.username)?, admin: user::is_admin::<DB>(&db, &credentials.username)?,
@ -257,19 +265,30 @@ fn recent(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::Directory>>, err
} }
#[get("/search")] #[get("/search")]
fn search_root(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> { fn search_root(
db: State<DB>,
_auth: Auth,
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::search(db.deref(), "")?; let result = index::search(db.deref(), "")?;
Ok(Json(result)) Ok(Json(result))
} }
#[get("/search/<query>")] #[get("/search/<query>")]
fn search(db: State<DB>, _auth: Auth, query: String) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> { fn search(
db: State<DB>,
_auth: Auth,
query: String,
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::search(db.deref(), &query)?; let result = index::search(db.deref(), &query)?;
Ok(Json(result)) Ok(Json(result))
} }
#[get("/serve/<path>")] #[get("/serve/<path>")]
fn serve(db: State<DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>, errors::Error> { fn serve(
db: State<DB>,
_auth: Auth,
path: VFSPathBuf,
) -> Result<serve::RangeResponder<File>, errors::Error> {
let db: &DB = db.deref(); let db: &DB = db.deref();
let vfs = db.get_vfs()?; let vfs = db.get_vfs()?;
let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?; let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
@ -290,8 +309,10 @@ struct ListPlaylistsEntry {
} }
#[get("/playlists")] #[get("/playlists")]
fn list_playlists(db: State<DB>, auth: Auth) -> Result<Json<Vec<ListPlaylistsEntry>>, errors::Error> { fn list_playlists(
db: State<DB>,
auth: Auth,
) -> Result<Json<Vec<ListPlaylistsEntry>>, errors::Error> {
let playlist_names = playlist::list_playlists(&auth.username, db.deref())?; let playlist_names = playlist::list_playlists(&auth.username, db.deref())?;
let playlists: Vec<ListPlaylistsEntry> = playlist_names let playlists: Vec<ListPlaylistsEntry> = playlist_names
.into_iter() .into_iter()
@ -307,13 +328,22 @@ struct SavePlaylistInput {
} }
#[put("/playlist/<name>", data = "<playlist>")] #[put("/playlist/<name>", data = "<playlist>")]
fn save_playlist(db: State<DB>, auth: Auth, name: String, playlist: Json<SavePlaylistInput>) -> Result<(), errors::Error> { fn save_playlist(
db: State<DB>,
auth: Auth,
name: String,
playlist: Json<SavePlaylistInput>,
) -> Result<(), errors::Error> {
playlist::save_playlist(&name, &auth.username, &playlist.tracks, db.deref())?; playlist::save_playlist(&name, &auth.username, &playlist.tracks, db.deref())?;
Ok(()) Ok(())
} }
#[get("/playlist/<name>")] #[get("/playlist/<name>")]
fn read_playlist(db: State<DB>, auth: Auth, name: String) -> Result<Json<Vec<index::Song>>, errors::Error> { fn read_playlist(
db: State<DB>,
auth: Auth,
name: String,
) -> Result<Json<Vec<index::Song>>, errors::Error> {
let songs = playlist::read_playlist(&name, &auth.username, db.deref())?; let songs = playlist::read_playlist(&name, &auth.username, db.deref())?;
Ok(Json(songs)) Ok(Json(songs))
} }
@ -323,3 +353,51 @@ fn delete_playlist(db: State<DB>, auth: Auth, name: String) -> Result<(), errors
playlist::delete_playlist(&name, &auth.username, db.deref())?; playlist::delete_playlist(&name, &auth.username, db.deref())?;
Ok(()) Ok(())
} }
#[put("/lastfm/now_playing/<path>")]
fn lastfm_now_playing(db: State<DB>, auth: Auth, path: VFSPathBuf) -> Result<(), errors::Error> {
lastfm::now_playing(db.deref(), &auth.username, &path.into() as &PathBuf)?;
Ok(())
}
#[post("/lastfm/scrobble/<path>")]
fn lastfm_scrobble(db: State<DB>, auth: Auth, path: VFSPathBuf) -> Result<(), errors::Error> {
lastfm::scrobble(db.deref(), &auth.username, &path.into() as &PathBuf)?;
Ok(())
}
#[get("/lastfm/link?<token>&<content>")]
fn lastfm_link(
db: State<DB>,
auth: Auth,
token: String,
content: String,
) -> Result<Html<String>, errors::Error> {
lastfm::link(db.deref(), &auth.username, &token)?;
// Percent decode
let base64_content = match RawStr::from_str(&content).percent_decode() {
Ok(s) => s,
Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()),
};
// Base64 decode
let popup_content = match base64::decode(base64_content.as_bytes()) {
Ok(c) => c,
Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()),
};
// UTF-8 decode
let popup_content_string = match str::from_utf8(&popup_content) {
Ok(s) => s,
Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()),
};
Ok(Html(popup_content_string.to_string()))
}
#[delete("/lastfm/link")]
fn lastfm_unlink(db: State<DB>, auth: Auth) -> Result<(), errors::Error> {
lastfm::unlink(db.deref(), &auth.username)?;
Ok(())
}