Genre endpoints WIP
This commit is contained in:
parent
cb35ef0ebb
commit
e06f79c500
9 changed files with 240 additions and 53 deletions
src
|
@ -104,6 +104,8 @@ pub enum Error {
|
|||
ArtistNotFound,
|
||||
#[error("Album not found")]
|
||||
AlbumNotFound,
|
||||
#[error("Genre not found")]
|
||||
GenreNotFound,
|
||||
#[error("Song not found")]
|
||||
SongNotFound,
|
||||
#[error("Invalid search query syntax")]
|
||||
|
|
|
@ -20,8 +20,8 @@ mod search;
|
|||
mod storage;
|
||||
|
||||
pub use browser::File;
|
||||
pub use collection::{Album, AlbumHeader, Artist, ArtistHeader, Song};
|
||||
use storage::{store_song, AlbumKey, ArtistKey, InternPath, SongKey};
|
||||
pub use collection::{Album, AlbumHeader, Artist, ArtistHeader, Genre, GenreHeader, Song};
|
||||
use storage::{store_song, AlbumKey, ArtistKey, GenreKey, InternPath, SongKey};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Manager {
|
||||
|
@ -108,6 +108,38 @@ impl Manager {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_genres(&self) -> Vec<GenreHeader> {
|
||||
spawn_blocking({
|
||||
let index_manager = self.clone();
|
||||
move || {
|
||||
let index = index_manager.index.read().unwrap();
|
||||
index.collection.get_genres(&index.strings)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_genre(&self, name: String) -> Result<Genre, Error> {
|
||||
spawn_blocking({
|
||||
let index_manager = self.clone();
|
||||
move || {
|
||||
let index = index_manager.index.read().unwrap();
|
||||
let name = index
|
||||
.strings
|
||||
.get(&name)
|
||||
.ok_or_else(|| Error::GenreNotFound)?;
|
||||
let genre_key = GenreKey { name };
|
||||
index
|
||||
.collection
|
||||
.get_genre(&index.strings, genre_key)
|
||||
.ok_or_else(|| Error::GenreNotFound)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_albums(&self) -> Vec<AlbumHeader> {
|
||||
spawn_blocking({
|
||||
let index_manager = self.clone();
|
||||
|
@ -137,12 +169,11 @@ impl Manager {
|
|||
let index_manager = self.clone();
|
||||
move || {
|
||||
let index = index_manager.index.read().unwrap();
|
||||
let artist_key = ArtistKey {
|
||||
name: match name.as_str() {
|
||||
"" => None,
|
||||
s => index.strings.get(s),
|
||||
},
|
||||
};
|
||||
let name = index
|
||||
.strings
|
||||
.get(name)
|
||||
.ok_or_else(|| Error::ArtistNotFound)?;
|
||||
let artist_key = ArtistKey { name };
|
||||
index
|
||||
.collection
|
||||
.get_artist(&index.strings, artist_key)
|
||||
|
@ -166,6 +197,7 @@ impl Manager {
|
|||
artists: artists
|
||||
.into_iter()
|
||||
.filter_map(|a| index.strings.get(a))
|
||||
.map(|k| ArtistKey { name: k })
|
||||
.collect(),
|
||||
name,
|
||||
};
|
||||
|
|
|
@ -4,16 +4,27 @@ use std::{
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use lasso2::{RodeoReader, Spur};
|
||||
use lasso2::RodeoReader;
|
||||
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinyvec::TinyVec;
|
||||
use unicase::UniCase;
|
||||
|
||||
use crate::app::index::storage::{self, AlbumKey, ArtistKey, SongKey};
|
||||
use crate::app::index::storage::{self, AlbumKey, ArtistKey, GenreKey, SongKey};
|
||||
|
||||
use super::storage::fetch_song;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct GenreHeader {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct Genre {
|
||||
pub header: GenreHeader,
|
||||
pub songs: Vec<Song>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct ArtistHeader {
|
||||
pub name: UniCase<String>,
|
||||
|
@ -70,6 +81,7 @@ pub struct Song {
|
|||
pub struct Collection {
|
||||
artists: HashMap<ArtistKey, storage::Artist>,
|
||||
albums: HashMap<AlbumKey, storage::Album>,
|
||||
genres: HashMap<GenreKey, storage::Genre>,
|
||||
songs: HashMap<SongKey, storage::Song>,
|
||||
recent_albums: Vec<AlbumKey>,
|
||||
}
|
||||
|
@ -178,6 +190,32 @@ impl Collection {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_genres(&self, strings: &RodeoReader) -> Vec<GenreHeader> {
|
||||
let mut genres = self
|
||||
.genres
|
||||
.values()
|
||||
.map(|a| make_genre_header(a, strings))
|
||||
.collect::<Vec<_>>();
|
||||
genres.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
genres
|
||||
}
|
||||
|
||||
pub fn get_genre(&self, strings: &RodeoReader, genre_key: GenreKey) -> Option<Genre> {
|
||||
self.genres.get(&genre_key).map(|genre| {
|
||||
let songs = genre
|
||||
.songs
|
||||
.iter()
|
||||
.filter_map(|k| self.get_song(strings, *k))
|
||||
.collect::<Vec<_>>();
|
||||
// TODO sort songs
|
||||
|
||||
Genre {
|
||||
header: make_genre_header(genre, strings),
|
||||
songs,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_song(&self, strings: &RodeoReader, song_key: SongKey) -> Option<Song> {
|
||||
self.songs.get(&song_key).map(|s| fetch_song(strings, s))
|
||||
}
|
||||
|
@ -194,7 +232,7 @@ fn make_album_header(album: &storage::Album, strings: &RodeoReader) -> AlbumHead
|
|||
artists: album
|
||||
.artists
|
||||
.iter()
|
||||
.map(|a| strings.resolve(a).to_string())
|
||||
.map(|a| strings.resolve(&a.name).to_string())
|
||||
.collect(),
|
||||
year: album.year,
|
||||
date_added: album.date_added,
|
||||
|
@ -217,10 +255,17 @@ fn make_artist_header(artist: &storage::Artist, strings: &RodeoReader) -> Artist
|
|||
}
|
||||
}
|
||||
|
||||
fn make_genre_header(genre: &storage::Genre, strings: &RodeoReader) -> GenreHeader {
|
||||
GenreHeader {
|
||||
name: strings.resolve(&genre.name).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
artists: HashMap<ArtistKey, storage::Artist>,
|
||||
albums: HashMap<AlbumKey, storage::Album>,
|
||||
genres: HashMap<GenreKey, storage::Genre>,
|
||||
songs: HashMap<SongKey, storage::Song>,
|
||||
}
|
||||
|
||||
|
@ -228,6 +273,7 @@ impl Builder {
|
|||
pub fn add_song(&mut self, song: &storage::Song) {
|
||||
self.add_song_to_album(&song);
|
||||
self.add_song_to_artists(&song);
|
||||
self.add_song_to_genres(&song);
|
||||
|
||||
self.songs.insert(
|
||||
SongKey {
|
||||
|
@ -249,6 +295,7 @@ impl Builder {
|
|||
Collection {
|
||||
artists: self.artists,
|
||||
albums: self.albums,
|
||||
genres: self.genres,
|
||||
songs: self.songs,
|
||||
recent_albums,
|
||||
}
|
||||
|
@ -257,39 +304,39 @@ impl Builder {
|
|||
fn add_song_to_artists(&mut self, song: &storage::Song) {
|
||||
let album_key = song.album_key();
|
||||
|
||||
let mut all_artists = TinyVec::<[Spur; 8]>::new();
|
||||
let mut all_artists = TinyVec::<[ArtistKey; 8]>::new();
|
||||
|
||||
for name in &song.album_artists {
|
||||
all_artists.push(*name);
|
||||
for artist_key in &song.album_artists {
|
||||
all_artists.push(*artist_key);
|
||||
if let Some(album_key) = &album_key {
|
||||
let artist = self.get_or_create_artist(*name);
|
||||
let artist = self.get_or_create_artist(*artist_key);
|
||||
artist.albums_as_performer.insert(album_key.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for name in &song.composers {
|
||||
all_artists.push(*name);
|
||||
for artist_key in &song.composers {
|
||||
all_artists.push(*artist_key);
|
||||
if let Some(album_key) = &album_key {
|
||||
let artist = self.get_or_create_artist(*name);
|
||||
let artist = self.get_or_create_artist(*artist_key);
|
||||
artist.albums_as_composer.insert(album_key.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for name in &song.lyricists {
|
||||
all_artists.push(*name);
|
||||
for artist_key in &song.lyricists {
|
||||
all_artists.push(*artist_key);
|
||||
if let Some(album_key) = &album_key {
|
||||
let artist = self.get_or_create_artist(*name);
|
||||
let artist = self.get_or_create_artist(*artist_key);
|
||||
artist.albums_as_lyricist.insert(album_key.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for name in &song.artists {
|
||||
all_artists.push(*name);
|
||||
for artist_key in &song.artists {
|
||||
all_artists.push(*artist_key);
|
||||
if let Some(album_key) = &album_key {
|
||||
let artist = self.get_or_create_artist(*name);
|
||||
let artist = self.get_or_create_artist(*artist_key);
|
||||
if song.album_artists.is_empty() {
|
||||
artist.albums_as_performer.insert(album_key.clone());
|
||||
} else if !song.album_artists.contains(name) {
|
||||
} else if !song.album_artists.contains(artist_key) {
|
||||
artist
|
||||
.albums_as_additional_performer
|
||||
.insert(album_key.clone());
|
||||
|
@ -297,8 +344,8 @@ impl Builder {
|
|||
}
|
||||
}
|
||||
|
||||
for name in all_artists {
|
||||
let artist = self.get_or_create_artist(name);
|
||||
for artist_key in all_artists {
|
||||
let artist = self.get_or_create_artist(artist_key);
|
||||
artist.num_songs += 1;
|
||||
if let Some(album_key) = &album_key {
|
||||
artist.all_albums.insert(album_key.clone());
|
||||
|
@ -313,12 +360,11 @@ impl Builder {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_or_create_artist(&mut self, name: lasso2::Spur) -> &mut storage::Artist {
|
||||
let artist_key = ArtistKey { name: Some(name) };
|
||||
fn get_or_create_artist(&mut self, artist_key: ArtistKey) -> &mut storage::Artist {
|
||||
self.artists
|
||||
.entry(artist_key)
|
||||
.or_insert_with(|| storage::Artist {
|
||||
name,
|
||||
name: artist_key.name,
|
||||
all_albums: HashSet::new(),
|
||||
albums_as_performer: HashSet::new(),
|
||||
albums_as_additional_performer: HashSet::new(),
|
||||
|
@ -359,6 +405,19 @@ impl Builder {
|
|||
virtual_path: song.virtual_path,
|
||||
});
|
||||
}
|
||||
|
||||
fn add_song_to_genres(&mut self, song: &storage::Song) {
|
||||
for name in &song.genres {
|
||||
let genre_key = GenreKey { name: *name };
|
||||
let genre = self.genres.entry(genre_key).or_insert(storage::Genre {
|
||||
name: *name,
|
||||
songs: Vec::new(),
|
||||
});
|
||||
genre.songs.push(SongKey {
|
||||
virtual_path: song.virtual_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -721,7 +780,7 @@ mod test {
|
|||
let artist = collection.get_artist(
|
||||
&strings,
|
||||
ArtistKey {
|
||||
name: strings.get("Stratovarius"),
|
||||
name: strings.get("Stratovarius").unwrap(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -787,7 +846,9 @@ mod test {
|
|||
let album = collection.get_album(
|
||||
&strings,
|
||||
AlbumKey {
|
||||
artists: tiny_vec![strings.get("FSOL").unwrap()],
|
||||
artists: tiny_vec![ArtistKey {
|
||||
name: strings.get("FSOL").unwrap()
|
||||
}],
|
||||
name: strings.get("Lifeforms").unwrap(),
|
||||
},
|
||||
);
|
||||
|
|
|
@ -282,24 +282,24 @@ impl Builder {
|
|||
self.text_fields[TextField::Album].insert(str, spur, song_key);
|
||||
}
|
||||
|
||||
for (str, spur) in scanner_song
|
||||
for (str, artist_key) in scanner_song
|
||||
.album_artists
|
||||
.iter()
|
||||
.zip(storage_song.album_artists.iter())
|
||||
{
|
||||
self.text_fields[TextField::AlbumArtist].insert(str, *spur, song_key);
|
||||
self.text_fields[TextField::AlbumArtist].insert(str, artist_key.name, song_key);
|
||||
}
|
||||
|
||||
for (str, spur) in scanner_song.artists.iter().zip(storage_song.artists.iter()) {
|
||||
self.text_fields[TextField::Artist].insert(str, *spur, song_key);
|
||||
for (str, artist_key) in scanner_song.artists.iter().zip(storage_song.artists.iter()) {
|
||||
self.text_fields[TextField::Artist].insert(str, artist_key.name, song_key);
|
||||
}
|
||||
|
||||
for (str, spur) in scanner_song
|
||||
for (str, artist_key) in scanner_song
|
||||
.composers
|
||||
.iter()
|
||||
.zip(storage_song.composers.iter())
|
||||
{
|
||||
self.text_fields[TextField::Composer].insert(str, *spur, song_key);
|
||||
self.text_fields[TextField::Composer].insert(str, artist_key.name, song_key);
|
||||
}
|
||||
|
||||
if let Some(disc_number) = &scanner_song.disc_number {
|
||||
|
@ -314,12 +314,12 @@ impl Builder {
|
|||
self.text_fields[TextField::Label].insert(str, *spur, song_key);
|
||||
}
|
||||
|
||||
for (str, spur) in scanner_song
|
||||
for (str, artist_key) in scanner_song
|
||||
.lyricists
|
||||
.iter()
|
||||
.zip(storage_song.lyricists.iter())
|
||||
{
|
||||
self.text_fields[TextField::Lyricist].insert(str, *spur, song_key);
|
||||
self.text_fields[TextField::Lyricist].insert(str, artist_key.name, song_key);
|
||||
}
|
||||
|
||||
self.text_fields[TextField::Path].insert(
|
||||
|
|
|
@ -16,6 +16,12 @@ pub enum File {
|
|||
Song(PathKey),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Genre {
|
||||
pub name: Spur,
|
||||
pub songs: Vec<SongKey>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Artist {
|
||||
pub name: Spur,
|
||||
|
@ -32,7 +38,7 @@ pub struct Artist {
|
|||
pub struct Album {
|
||||
pub name: Spur,
|
||||
pub artwork: Option<PathKey>,
|
||||
pub artists: TinyVec<[Spur; 1]>,
|
||||
pub artists: TinyVec<[ArtistKey; 1]>,
|
||||
pub year: Option<i64>,
|
||||
pub date_added: i64,
|
||||
pub songs: HashSet<SongKey>,
|
||||
|
@ -45,14 +51,14 @@ pub struct Song {
|
|||
pub track_number: Option<i64>,
|
||||
pub disc_number: Option<i64>,
|
||||
pub title: Option<Spur>,
|
||||
pub artists: TinyVec<[Spur; 1]>,
|
||||
pub album_artists: TinyVec<[Spur; 1]>,
|
||||
pub artists: TinyVec<[ArtistKey; 1]>,
|
||||
pub album_artists: TinyVec<[ArtistKey; 1]>,
|
||||
pub year: Option<i64>,
|
||||
pub album: Option<Spur>,
|
||||
pub artwork: Option<PathKey>,
|
||||
pub duration: Option<i64>,
|
||||
pub lyricists: TinyVec<[Spur; 0]>,
|
||||
pub composers: TinyVec<[Spur; 0]>,
|
||||
pub lyricists: TinyVec<[ArtistKey; 0]>,
|
||||
pub composers: TinyVec<[ArtistKey; 0]>,
|
||||
pub genres: TinyVec<[Spur; 1]>,
|
||||
pub labels: TinyVec<[Spur; 0]>,
|
||||
pub date_added: i64,
|
||||
|
@ -63,14 +69,19 @@ pub struct Song {
|
|||
)]
|
||||
pub struct PathKey(pub Spur);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct GenreKey {
|
||||
pub name: Spur,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ArtistKey {
|
||||
pub name: Option<Spur>,
|
||||
pub name: Spur,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AlbumKey {
|
||||
pub artists: TinyVec<[Spur; 4]>,
|
||||
pub artists: TinyVec<[ArtistKey; 4]>,
|
||||
pub name: Spur,
|
||||
}
|
||||
|
||||
|
@ -151,11 +162,17 @@ pub fn store_song(
|
|||
track_number: song.track_number,
|
||||
disc_number: song.disc_number,
|
||||
title: song.title.as_ref().and_then(&mut canonicalize),
|
||||
artists: song.artists.iter().filter_map(&mut canonicalize).collect(),
|
||||
artists: song
|
||||
.artists
|
||||
.iter()
|
||||
.filter_map(&mut canonicalize)
|
||||
.map(|k| ArtistKey { name: k })
|
||||
.collect(),
|
||||
album_artists: song
|
||||
.album_artists
|
||||
.iter()
|
||||
.filter_map(&mut canonicalize)
|
||||
.map(|k| ArtistKey { name: k })
|
||||
.collect(),
|
||||
year: song.year,
|
||||
album: song.album.as_ref().and_then(&mut canonicalize),
|
||||
|
@ -165,11 +182,13 @@ pub fn store_song(
|
|||
.lyricists
|
||||
.iter()
|
||||
.filter_map(&mut canonicalize)
|
||||
.map(|k| ArtistKey { name: k })
|
||||
.collect(),
|
||||
composers: song
|
||||
.composers
|
||||
.iter()
|
||||
.filter_map(&mut canonicalize)
|
||||
.map(|k| ArtistKey { name: k })
|
||||
.collect(),
|
||||
genres: song.genres.iter().filter_map(&mut canonicalize).collect(),
|
||||
labels: song.labels.iter().filter_map(&mut canonicalize).collect(),
|
||||
|
@ -187,12 +206,12 @@ pub fn fetch_song(strings: &RodeoReader, song: &Song) -> super::Song {
|
|||
artists: song
|
||||
.artists
|
||||
.iter()
|
||||
.map(|s| strings.resolve(&s).to_string())
|
||||
.map(|k| strings.resolve(&k.name).to_string())
|
||||
.collect(),
|
||||
album_artists: song
|
||||
.album_artists
|
||||
.iter()
|
||||
.map(|s| strings.resolve(&s).to_string())
|
||||
.map(|k| strings.resolve(&k.name).to_string())
|
||||
.collect(),
|
||||
year: song.year,
|
||||
album: song.album.map(|s| strings.resolve(&s).to_string()),
|
||||
|
@ -201,12 +220,12 @@ pub fn fetch_song(strings: &RodeoReader, song: &Song) -> super::Song {
|
|||
lyricists: song
|
||||
.lyricists
|
||||
.iter()
|
||||
.map(|s| strings.resolve(&s).to_string())
|
||||
.map(|k| strings.resolve(&k.name).to_string())
|
||||
.collect(),
|
||||
composers: song
|
||||
.composers
|
||||
.iter()
|
||||
.map(|s| strings.resolve(&s).to_string())
|
||||
.map(|k| strings.resolve(&k.name).to_string())
|
||||
.collect(),
|
||||
genres: song
|
||||
.genres
|
||||
|
|
|
@ -59,6 +59,9 @@ pub fn router() -> Router<App> {
|
|||
.route("/artists", get(get_artists))
|
||||
.route("/artists/:artist", get(get_artist))
|
||||
.route("/artists/:artists/albums/:name", get(get_album))
|
||||
.route("/genres", get(get_genres))
|
||||
.route("/genres/:genre", get(get_genre))
|
||||
.route("/genres/:genre/songs", get(get_genre_songs))
|
||||
.route("/random", get(get_random_albums)) // Deprecated
|
||||
.route("/recent", get(get_recent_albums)) // Deprecated
|
||||
// Search
|
||||
|
@ -508,6 +511,45 @@ async fn get_recent_albums(
|
|||
albums_to_response(albums, api_version)
|
||||
}
|
||||
|
||||
async fn get_genres(
|
||||
_auth: Auth,
|
||||
State(index_manager): State<index::Manager>,
|
||||
) -> Result<Json<Vec<dto::GenreHeader>>, APIError> {
|
||||
Ok(Json(
|
||||
index_manager
|
||||
.get_genres()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|g| g.into())
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_genre(
|
||||
_auth: Auth,
|
||||
State(index_manager): State<index::Manager>,
|
||||
Path(genre): Path<String>,
|
||||
) -> Result<Json<dto::Genre>, APIError> {
|
||||
Ok(Json(index_manager.get_genre(genre).await?.into()))
|
||||
}
|
||||
|
||||
async fn get_genre_songs(
|
||||
_auth: Auth,
|
||||
State(index_manager): State<index::Manager>,
|
||||
Path(genre): Path<String>,
|
||||
) -> Result<Json<dto::SongList>, APIError> {
|
||||
let songs = index_manager.get_genre(genre).await?.songs;
|
||||
let song_list = dto::SongList {
|
||||
paths: songs.iter().map(|s| s.virtual_path.clone()).collect(),
|
||||
first_songs: songs
|
||||
.into_iter()
|
||||
.take(SONG_LIST_CAPACITY)
|
||||
.map(|s| s.into())
|
||||
.collect(),
|
||||
};
|
||||
Ok(Json(song_list))
|
||||
}
|
||||
|
||||
async fn get_search(
|
||||
_auth: Auth,
|
||||
api_version: APIMajorVersion,
|
||||
|
|
|
@ -23,6 +23,7 @@ impl IntoResponse for APIError {
|
|||
APIError::DirectoryNotFound(_) => StatusCode::NOT_FOUND,
|
||||
APIError::ArtistNotFound => StatusCode::NOT_FOUND,
|
||||
APIError::AlbumNotFound => StatusCode::NOT_FOUND,
|
||||
APIError::GenreNotFound => StatusCode::NOT_FOUND,
|
||||
APIError::SongNotFound => StatusCode::NOT_FOUND,
|
||||
APIError::EmbeddedArtworkNotFound => StatusCode::NOT_FOUND,
|
||||
APIError::EmptyPassword => StatusCode::BAD_REQUEST,
|
||||
|
|
|
@ -317,6 +317,33 @@ impl From<index::File> for BrowserEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GenreHeader {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<index::GenreHeader> for GenreHeader {
|
||||
fn from(g: index::GenreHeader) -> Self {
|
||||
Self {
|
||||
name: g.name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Genre {
|
||||
#[serde(flatten)]
|
||||
pub header: GenreHeader,
|
||||
}
|
||||
|
||||
impl From<index::Genre> for Genre {
|
||||
fn from(genre: index::Genre) -> Self {
|
||||
Self {
|
||||
header: GenreHeader::from(genre.header),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ArtistHeader {
|
||||
pub name: String,
|
||||
|
|
|
@ -29,6 +29,8 @@ pub enum APIError {
|
|||
ArtistNotFound,
|
||||
#[error("Album not found")]
|
||||
AlbumNotFound,
|
||||
#[error("Genre not found")]
|
||||
GenreNotFound,
|
||||
#[error("Song not found")]
|
||||
SongNotFound,
|
||||
#[error("DDNS update query failed with HTTP status {0}")]
|
||||
|
@ -137,6 +139,7 @@ impl From<app::Error> for APIError {
|
|||
app::Error::DirectoryNotFound(d) => APIError::DirectoryNotFound(d),
|
||||
app::Error::ArtistNotFound => APIError::ArtistNotFound,
|
||||
app::Error::AlbumNotFound => APIError::AlbumNotFound,
|
||||
app::Error::GenreNotFound => APIError::GenreNotFound,
|
||||
app::Error::SongNotFound => APIError::SongNotFound,
|
||||
app::Error::PlaylistNotFound => APIError::PlaylistNotFound,
|
||||
app::Error::SearchQueryParseError => APIError::SearchQueryParseError,
|
||||
|
|
Loading…
Add table
Reference in a new issue