Adds get_albums endpoint
This commit is contained in:
parent
e65cee366d
commit
2b81355f6d
5 changed files with 120 additions and 31 deletions
|
@ -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();
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue