diff --git a/src/library/mod.rs b/src/library/mod.rs index ead4cb7..b2e1615 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -166,6 +166,127 @@ impl Libary { Track::get(track_id).await } + pub async fn reload_metadata(&self, track_id: &str) -> Result<(), ()> { + let mut track = Track::get(track_id).await.ok_or_else(|| ())?; + let path = &track.path; + log::info!("Rescanning metadata for {path}"); + + let metadata = metadata::get_metadata(path); + + let mut update = json!({}); + + if let Some(meta) = &metadata { + if let Some(artist) = meta.artist() { + let artist_id = self.find_or_create_artist(artist).await; + update + .as_object_mut() + .unwrap() + .insert("artist_id".into(), artist_id.into()); + } else { + log::warn!("{path} has no artist"); + } + + if let Some(album) = meta.album() { + let album_id = self + .find_or_create_album( + album, + update + .as_object() + .unwrap() + .get("artist_id") + .map(|x| x.as_str().unwrap()), + ) + .await; + update + .as_object_mut() + .unwrap() + .insert("album_id".into(), album_id.into()); + } else { + log::warn!("{path} has no album and will be treated as single"); + } + + if let Some(title) = meta.title() { + update + .as_object_mut() + .unwrap() + .insert("title".into(), title.into()); + } + } + + if let Some(metadata) = metadata { + update + .as_object_mut() + .unwrap() + .insert("meta".into(), metadata.0); + } + + // if no title in metadata use file name + if update.as_object().unwrap().get("title").is_none() { + update.as_object_mut().unwrap().insert( + "title".into(), + std::path::Path::new(&path) + .file_stem() + .unwrap() + .to_str() + .unwrap() + .into(), + ); + } + + track.update(&update).await.unwrap(); + + Ok(()) + } + + pub async fn clean_lost_files(&self) { + // todo : clean + // tracks + for track in Track::find(doc! {}, None).await.unwrap() { + if !std::path::Path::new(&track.path).exists() { + log::info!("Cleaning lost {}", track.path); + track.delete().await.unwrap(); + } + } + // albums + for album in Album::find_partial(doc! {}, json!({"title": 1}), None) + .await + .unwrap() + { + if Track::find_partial(doc! { "album_id": album.reference() }, json!({}), None) + .await + .unwrap() + .is_empty() + { + log::info!( + "Cleaning album {} with no tracks", + album.title.as_ref().unwrap() + ); + Album::remove(album.id()).await.unwrap(); + } + } + // artists + for artist in Artist::find_partial(doc! {}, json!({"name": 1}), None) + .await + .unwrap() + { + if Track::find_partial(doc! { "artist_id": artist.reference()}, json!({}), None) + .await + .unwrap() + .is_empty() + && Album::find_partial(doc! { "artist_id": artist.reference()}, json!({}), None) + .await + .unwrap() + .is_empty() + { + log::info!( + "Cleaning artist {} with no tracks or albums", + artist.name.as_ref().unwrap() + ); + Artist::remove(artist.id()).await.unwrap(); + } + } + } + pub async fn rescan(&self, cache: &RouteCache) { cache.invalidate("albums", "latest").await; diff --git a/src/main.rs b/src/main.rs index c771f0e..6b27231 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,7 @@ async fn rocket() -> _ { route::album::latest_albums_route, route::track::track_route, route::track::track_audio_route, + route::track::track_reload_meta_route, route::album::album_cover_route, route::user::login_route, route::user::passwd_route, @@ -64,7 +65,8 @@ async fn rocket() -> _ { route::playlist::playlist_route, route::playlist::playlist_add_route, route::playlist::playlist_edit_route, - route::playlist::playlist_tracks_route + route::playlist::playlist_tracks_route, + route::admin::clean_library ], ) .manage(lib) diff --git a/src/route/admin.rs b/src/route/admin.rs new file mode 100644 index 0000000..7e3965d --- /dev/null +++ b/src/route/admin.rs @@ -0,0 +1,16 @@ +use super::api_error; +use super::FallibleApiResponse; +use mongodb::bson::doc; +use rocket::{get, State}; +use serde_json::json; + +use crate::check_admin; +use crate::library::user::User; +use crate::library::Libary; + +#[get("/library/clean")] +pub async fn clean_library(lib: &State, u: User) -> FallibleApiResponse { + check_admin!(u); + lib.clean_lost_files().await; + Ok(json!({"ok": 1})) +} diff --git a/src/route/mod.rs b/src/route/mod.rs index 2602d6c..91edffd 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -5,6 +5,7 @@ use rocket::{ }; use serde_json::json; +pub mod admin; pub mod album; pub mod artist; pub mod playlist; diff --git a/src/route/track.rs b/src/route/track.rs index ede01af..7346fb0 100644 --- a/src/route/track.rs +++ b/src/route/track.rs @@ -1,10 +1,13 @@ use super::api_error; use super::FallibleApiResponse; use super::ToAPI; +use crate::library::user::User; use fs::NamedFile; use mongodb::bson::doc; use rocket::{fs, get, State}; +use serde_json::json; +use crate::check_admin; use crate::library::Libary; #[get("/track/")] @@ -17,6 +20,19 @@ pub async fn track_route(track_id: &str, lib: &State) -> FallibleApiResp .await) } +#[get("/track//reload")] +pub async fn track_reload_meta_route( + track_id: &str, + lib: &State, + u: User, +) -> FallibleApiResponse { + check_admin!(u); + lib.reload_metadata(track_id) + .await + .map_err(|_| api_error("Error reloading metadata"))?; + Ok(json!({"ok": 1})) +} + #[get("/track//audio")] pub async fn track_audio_route(track_id: &str, lib: &State) -> Option { let track = lib.get_track_by_id(track_id).await?; diff --git a/src/route/user.rs b/src/route/user.rs index cddbe07..bffcaa0 100644 --- a/src/route/user.rs +++ b/src/route/user.rs @@ -16,6 +16,7 @@ use serde_json::json; use super::api_error; use super::FallibleApiResponse; +#[macro_export] macro_rules! check_admin { ($u:ident) => { if !$u.is_admin() {