From 2b81355f6d07e373b3aa2988ad0f2afad1a51aad Mon Sep 17 00:00:00 2001 From: Antoine Gersant Date: Mon, 16 Sep 2024 23:25:41 -0700 Subject: [PATCH] Adds get_albums endpoint --- src/app/index.rs | 14 +++++- src/app/index/collection.rs | 95 ++++++++++++++++++++++++++++++------- src/server/axum/api.rs | 24 ++++++++-- src/server/dto/v7.rs | 10 ++-- src/server/dto/v8.rs | 8 ++-- 5 files changed, 120 insertions(+), 31 deletions(-) diff --git a/src/app/index.rs b/src/app/index.rs index 5519b3b..5affd11 100644 --- a/src/app/index.rs +++ b/src/app/index.rs @@ -19,7 +19,7 @@ mod search; mod storage; pub use browser::File; -pub use collection::{Album, Artist, ArtistHeader, Song}; +pub use collection::{Album, AlbumHeader, Artist, ArtistHeader, Song}; use storage::{AlbumKey, ArtistKey, InternPath, SongKey}; #[derive(Clone)] @@ -107,6 +107,18 @@ impl Manager { .unwrap() } + pub async fn get_albums(&self) -> Vec { + spawn_blocking({ + let index_manager = self.clone(); + move || { + let index = index_manager.index.read().unwrap(); + index.collection.get_albums(&index.strings) + } + }) + .await + .unwrap() + } + pub async fn get_artists(&self) -> Vec { spawn_blocking({ let index_manager = self.clone(); diff --git a/src/app/index/collection.rs b/src/app/index/collection.rs index 3c51157..a77b1a5 100644 --- a/src/app/index/collection.rs +++ b/src/app/index/collection.rs @@ -33,12 +33,17 @@ pub struct Artist { } #[derive(Debug, Default, PartialEq, Eq)] -pub struct Album { +pub struct AlbumHeader { pub name: String, pub artwork: Option, pub artists: Vec, pub year: Option, pub date_added: i64, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Album { + pub header: AlbumHeader, pub songs: Vec, } @@ -71,6 +76,16 @@ pub struct Collection { } impl Collection { + pub fn get_albums(&self, strings: &RodeoReader) -> Vec { + let mut albums = self + .albums + .values() + .map(|a| make_album_header(a, strings)) + .collect::>(); + albums.sort_by(|a, b| a.name.cmp(&b.name)); + albums + } + pub fn get_artists(&self, strings: &RodeoReader) -> Vec { let exceptions = vec![strings.get("Various Artists"), strings.get("VA")]; let mut artists = self @@ -92,7 +107,9 @@ impl Collection { .iter() .filter_map(|key| self.get_album(strings, key.clone())) .collect::>(); - albums.sort_by(|a, b| (&a.year, &a.name).cmp(&(&b.year, &b.name))); + albums.sort_by(|a, b| { + (&a.header.year, &a.header.name).cmp(&(&b.header.year, &b.header.name)) + }); albums }; Artist { header, albums } @@ -117,19 +134,7 @@ impl Collection { songs.sort_by_key(|s| (s.disc_number.unwrap_or(-1), s.track_number.unwrap_or(-1))); Album { - name: strings.resolve(&a.name).to_string(), - artwork: a - .artwork - .as_ref() - .map(|a| strings.resolve(&a.0)) - .map(PathBuf::from), - artists: a - .artists - .iter() - .map(|a| strings.resolve(a).to_string()) - .collect(), - year: a.year, - date_added: a.date_added, + header: make_album_header(a, strings), songs, } }) @@ -157,6 +162,24 @@ impl Collection { } } +fn make_album_header(album: &storage::Album, strings: &RodeoReader) -> AlbumHeader { + AlbumHeader { + name: strings.resolve(&album.name).to_string(), + artwork: album + .artwork + .as_ref() + .map(|a| strings.resolve(&a.0)) + .map(PathBuf::from), + artists: album + .artists + .iter() + .map(|a| strings.resolve(a).to_string()) + .collect(), + year: album.year, + date_added: album.date_added, + } +} + fn make_artist_header(artist: &storage::Artist, strings: &RodeoReader) -> ArtistHeader { ArtistHeader { name: UniCase::new(strings.resolve(&artist.name).to_owned()), @@ -473,6 +496,36 @@ mod test { ); } + #[test] + fn can_get_all_albums() { + let (collection, strings) = setup_test(Vec::from([ + scanner::Song { + album: Some("ISDN".to_owned()), + ..Default::default() + }, + scanner::Song { + album: Some("Elysium".to_owned()), + ..Default::default() + }, + scanner::Song { + album: Some("Lifeforms".to_owned()), + ..Default::default() + }, + ])); + + let albums = collection.get_albums(&strings); + assert_eq!(albums.len(), 3); + + assert_eq!( + albums.into_iter().map(|a| a.name).collect::>(), + vec![ + "Elysium".to_owned(), + "ISDN".to_owned(), + "Lifeforms".to_owned() + ] + ); + } + #[test] fn can_get_random_albums() { let (collection, strings) = setup_test(Vec::from([ @@ -490,7 +543,10 @@ mod test { assert_eq!(albums.len(), 2); assert_eq!( - albums.into_iter().map(|a| a.name).collect::>(), + albums + .into_iter() + .map(|a| a.header.name) + .collect::>(), HashSet::from_iter(["ISDN".to_owned(), "Lifeforms".to_owned()]) ); } @@ -514,7 +570,10 @@ mod test { assert_eq!(albums.len(), 2); assert_eq!( - albums.into_iter().map(|a| a.name).collect::>(), + albums + .into_iter() + .map(|a| a.header.name) + .collect::>(), vec!["ISDN".to_owned(), "Lifeforms".to_owned()] ); } @@ -647,7 +706,7 @@ mod test { .unwrap() .albums .into_iter() - .map(|a| a.name) + .map(|a| a.header.name) .collect::>(); assert_eq!( diff --git a/src/server/axum/api.rs b/src/server/axum/api.rs index 1bef9b4..9995d7e 100644 --- a/src/server/axum/api.rs +++ b/src/server/axum/api.rs @@ -53,11 +53,14 @@ pub fn router() -> Router { .route("/flatten", get(get_flatten_root)) .route("/flatten/*path", get(get_flatten)) // Semantic + .route("/albums", get(get_albums)) + .route("/albums/recent", get(get_recent)) + .route("/albums/random", get(get_random)) .route("/artists", get(get_artists)) .route("/artists/:artist", get(get_artist)) .route("/artists/:artists/albums/:name", get(get_album)) - .route("/random", get(get_random)) - .route("/recent", get(get_recent)) + .route("/random", get(get_random)) // Deprecated + .route("/recent", get(get_recent)) // Deprecated // Search .route("/search", get(get_search_root)) .route("/search/*query", get(get_search)) @@ -328,7 +331,7 @@ fn albums_to_response(albums: Vec, api_version: APIMajorVersion) - APIMajorVersion::V8 => Json( albums .into_iter() - .map(|f| f.into()) + .map(|f| f.header.into()) .collect::>(), ) .into_response(), @@ -387,6 +390,21 @@ async fn get_flatten( song_list_to_response(song_list, api_version) } +async fn get_albums( + _auth: Auth, + State(index_manager): State, +) -> Result>, APIError> { + Ok(Json( + index_manager + .get_albums() + .await + .into_iter() + .map(|a| a.into()) + .collect::>() + .into(), + )) +} + async fn get_artists( _auth: Auth, State(index_manager): State, diff --git a/src/server/dto/v7.rs b/src/server/dto/v7.rs index 71d35c2..81068a8 100644 --- a/src/server/dto/v7.rs +++ b/src/server/dto/v7.rs @@ -358,13 +358,13 @@ impl From for Directory { Self { path, - artist: match album.artists.is_empty() { + artist: match album.header.artists.is_empty() { true => None, - false => Some(album.artists.join("")), + false => Some(album.header.artists.join("")), }, - year: album.year, - album: Some(album.name), - artwork: album.artwork, + year: album.header.year, + album: Some(album.header.name), + artwork: album.header.artwork, } } } diff --git a/src/server/dto/v8.rs b/src/server/dto/v8.rs index 7160fc4..2d45e96 100644 --- a/src/server/dto/v8.rs +++ b/src/server/dto/v8.rs @@ -377,7 +377,7 @@ impl From for Artist { lyricist: song.lyricists.contains(&artist_name), }) .collect(), - album: AlbumHeader::from(album), + album: AlbumHeader::from(album.header), }; Self { header: ArtistHeader::from(artist.header), @@ -397,8 +397,8 @@ pub struct AlbumHeader { pub year: Option, } -impl From for AlbumHeader { - fn from(a: index::Album) -> Self { +impl From for AlbumHeader { + fn from(a: index::AlbumHeader) -> Self { Self { name: a.name, artwork: a.artwork.map(|a| a.to_string_lossy().to_string()), @@ -419,7 +419,7 @@ impl From for Album { fn from(mut a: index::Album) -> Self { let songs = a.songs.drain(..).map(|s| s.into()).collect(); Self { - header: a.into(), + header: a.header.into(), songs: songs, } }