Adds get_albums endpoint

This commit is contained in:
Antoine Gersant 2024-09-16 23:25:41 -07:00
parent e65cee366d
commit 2b81355f6d
5 changed files with 120 additions and 31 deletions

View file

@ -19,7 +19,7 @@ mod search;
mod storage; mod storage;
pub use browser::File; 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}; use storage::{AlbumKey, ArtistKey, InternPath, SongKey};
#[derive(Clone)] #[derive(Clone)]
@ -107,6 +107,18 @@ impl Manager {
.unwrap() .unwrap()
} }
pub async fn get_albums(&self) -> Vec<AlbumHeader> {
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<ArtistHeader> { pub async fn get_artists(&self) -> Vec<ArtistHeader> {
spawn_blocking({ spawn_blocking({
let index_manager = self.clone(); let index_manager = self.clone();

View file

@ -33,12 +33,17 @@ pub struct Artist {
} }
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
pub struct Album { pub struct AlbumHeader {
pub name: String, pub name: String,
pub artwork: Option<PathBuf>, pub artwork: Option<PathBuf>,
pub artists: Vec<String>, pub artists: Vec<String>,
pub year: Option<i64>, pub year: Option<i64>,
pub date_added: i64, pub date_added: i64,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Album {
pub header: AlbumHeader,
pub songs: Vec<Song>, pub songs: Vec<Song>,
} }
@ -71,6 +76,16 @@ pub struct Collection {
} }
impl Collection { impl Collection {
pub fn get_albums(&self, strings: &RodeoReader) -> Vec<AlbumHeader> {
let mut albums = self
.albums
.values()
.map(|a| make_album_header(a, strings))
.collect::<Vec<_>>();
albums.sort_by(|a, b| a.name.cmp(&b.name));
albums
}
pub fn get_artists(&self, strings: &RodeoReader) -> Vec<ArtistHeader> { pub fn get_artists(&self, strings: &RodeoReader) -> Vec<ArtistHeader> {
let exceptions = vec![strings.get("Various Artists"), strings.get("VA")]; let exceptions = vec![strings.get("Various Artists"), strings.get("VA")];
let mut artists = self let mut artists = self
@ -92,7 +107,9 @@ impl Collection {
.iter() .iter()
.filter_map(|key| self.get_album(strings, key.clone())) .filter_map(|key| self.get_album(strings, key.clone()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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 albums
}; };
Artist { header, 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))); songs.sort_by_key(|s| (s.disc_number.unwrap_or(-1), s.track_number.unwrap_or(-1)));
Album { Album {
name: strings.resolve(&a.name).to_string(), header: make_album_header(a, strings),
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,
songs, 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 { fn make_artist_header(artist: &storage::Artist, strings: &RodeoReader) -> ArtistHeader {
ArtistHeader { ArtistHeader {
name: UniCase::new(strings.resolve(&artist.name).to_owned()), 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<_>>(),
vec![
"Elysium".to_owned(),
"ISDN".to_owned(),
"Lifeforms".to_owned()
]
);
}
#[test] #[test]
fn can_get_random_albums() { fn can_get_random_albums() {
let (collection, strings) = setup_test(Vec::from([ let (collection, strings) = setup_test(Vec::from([
@ -490,7 +543,10 @@ mod test {
assert_eq!(albums.len(), 2); assert_eq!(albums.len(), 2);
assert_eq!( assert_eq!(
albums.into_iter().map(|a| a.name).collect::<HashSet<_>>(), albums
.into_iter()
.map(|a| a.header.name)
.collect::<HashSet<_>>(),
HashSet::from_iter(["ISDN".to_owned(), "Lifeforms".to_owned()]) HashSet::from_iter(["ISDN".to_owned(), "Lifeforms".to_owned()])
); );
} }
@ -514,7 +570,10 @@ mod test {
assert_eq!(albums.len(), 2); assert_eq!(albums.len(), 2);
assert_eq!( assert_eq!(
albums.into_iter().map(|a| a.name).collect::<Vec<_>>(), albums
.into_iter()
.map(|a| a.header.name)
.collect::<Vec<_>>(),
vec!["ISDN".to_owned(), "Lifeforms".to_owned()] vec!["ISDN".to_owned(), "Lifeforms".to_owned()]
); );
} }
@ -647,7 +706,7 @@ mod test {
.unwrap() .unwrap()
.albums .albums
.into_iter() .into_iter()
.map(|a| a.name) .map(|a| a.header.name)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!( assert_eq!(

View file

@ -53,11 +53,14 @@ pub fn router() -> Router<App> {
.route("/flatten", get(get_flatten_root)) .route("/flatten", get(get_flatten_root))
.route("/flatten/*path", get(get_flatten)) .route("/flatten/*path", get(get_flatten))
// Semantic // 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", get(get_artists))
.route("/artists/:artist", get(get_artist)) .route("/artists/:artist", get(get_artist))
.route("/artists/:artists/albums/:name", get(get_album)) .route("/artists/:artists/albums/:name", get(get_album))
.route("/random", get(get_random)) .route("/random", get(get_random)) // Deprecated
.route("/recent", get(get_recent)) .route("/recent", get(get_recent)) // Deprecated
// Search // Search
.route("/search", get(get_search_root)) .route("/search", get(get_search_root))
.route("/search/*query", get(get_search)) .route("/search/*query", get(get_search))
@ -328,7 +331,7 @@ fn albums_to_response(albums: Vec<index::Album>, api_version: APIMajorVersion) -
APIMajorVersion::V8 => Json( APIMajorVersion::V8 => Json(
albums albums
.into_iter() .into_iter()
.map(|f| f.into()) .map(|f| f.header.into())
.collect::<Vec<dto::AlbumHeader>>(), .collect::<Vec<dto::AlbumHeader>>(),
) )
.into_response(), .into_response(),
@ -387,6 +390,21 @@ async fn get_flatten(
song_list_to_response(song_list, api_version) song_list_to_response(song_list, api_version)
} }
async fn get_albums(
_auth: Auth,
State(index_manager): State<index::Manager>,
) -> Result<Json<Vec<dto::AlbumHeader>>, APIError> {
Ok(Json(
index_manager
.get_albums()
.await
.into_iter()
.map(|a| a.into())
.collect::<Vec<_>>()
.into(),
))
}
async fn get_artists( async fn get_artists(
_auth: Auth, _auth: Auth,
State(index_manager): State<index::Manager>, State(index_manager): State<index::Manager>,

View file

@ -358,13 +358,13 @@ impl From<index::Album> for Directory {
Self { Self {
path, path,
artist: match album.artists.is_empty() { artist: match album.header.artists.is_empty() {
true => None, true => None,
false => Some(album.artists.join("")), false => Some(album.header.artists.join("")),
}, },
year: album.year, year: album.header.year,
album: Some(album.name), album: Some(album.header.name),
artwork: album.artwork, artwork: album.header.artwork,
} }
} }
} }

View file

@ -377,7 +377,7 @@ impl From<index::Artist> for Artist {
lyricist: song.lyricists.contains(&artist_name), lyricist: song.lyricists.contains(&artist_name),
}) })
.collect(), .collect(),
album: AlbumHeader::from(album), album: AlbumHeader::from(album.header),
}; };
Self { Self {
header: ArtistHeader::from(artist.header), header: ArtistHeader::from(artist.header),
@ -397,8 +397,8 @@ pub struct AlbumHeader {
pub year: Option<i64>, pub year: Option<i64>,
} }
impl From<index::Album> for AlbumHeader { impl From<index::AlbumHeader> for AlbumHeader {
fn from(a: index::Album) -> Self { fn from(a: index::AlbumHeader) -> Self {
Self { Self {
name: a.name, name: a.name,
artwork: a.artwork.map(|a| a.to_string_lossy().to_string()), artwork: a.artwork.map(|a| a.to_string_lossy().to_string()),
@ -419,7 +419,7 @@ impl From<index::Album> for Album {
fn from(mut a: index::Album) -> Self { fn from(mut a: index::Album) -> Self {
let songs = a.songs.drain(..).map(|s| s.into()).collect(); let songs = a.songs.drain(..).map(|s| s.into()).collect();
Self { Self {
header: a.into(), header: a.header.into(),
songs: songs, songs: songs,
} }
} }