Intern strings in collection

This commit is contained in:
Antoine Gersant 2024-08-09 10:25:18 -07:00
parent 0a1f3fa78d
commit 6821318a4d
8 changed files with 262 additions and 190 deletions

1
Cargo.lock generated
View file

@ -2676,6 +2676,7 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [ dependencies = [
"serde",
"tinyvec_macros", "tinyvec_macros",
] ]

View file

@ -37,7 +37,7 @@ serde = { version = "1.0.147", features = ["derive"] }
serde_derive = "1.0.147" serde_derive = "1.0.147"
serde_json = "1.0.122" serde_json = "1.0.122"
simplelog = "0.12.2" simplelog = "0.12.2"
tinyvec = "1.8.0" tinyvec = { version = "1.8.0", features = ["serde"] }
thiserror = "1.0.62" thiserror = "1.0.62"
tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
tokio-util = { version = "0.7.11", features = ["io"] } tokio-util = { version = "0.7.11", features = ["io"] }

View file

@ -103,15 +103,20 @@ impl Manager {
.unwrap() .unwrap()
} }
pub async fn get_artist(&self, artist_key: &ArtistKey) -> Result<Artist, Error> { pub async fn get_artist(&self, name: String) -> Result<Artist, Error> {
spawn_blocking({ spawn_blocking({
let index_manager = self.clone(); let index_manager = self.clone();
let artist_id = artist_key.into();
move || { move || {
let index = index_manager.index.read().unwrap(); let index = index_manager.index.read().unwrap();
let artist_key = ArtistKey {
name: match name.as_str() {
"" => None,
s => index.strings.get(s),
},
};
index index
.collection .collection
.get_artist(artist_id) .get_artist(&index.strings, artist_key)
.ok_or_else(|| Error::ArtistNotFound) .ok_or_else(|| Error::ArtistNotFound)
} }
}) })
@ -119,15 +124,25 @@ impl Manager {
.unwrap() .unwrap()
} }
pub async fn get_album(&self, album_key: &AlbumKey) -> Result<Album, Error> { pub async fn get_album(
&self,
artists: Vec<String>,
name: Option<String>,
) -> Result<Album, Error> {
spawn_blocking({ spawn_blocking({
let index_manager = self.clone(); let index_manager = self.clone();
let album_id = album_key.into();
move || { move || {
let index = index_manager.index.read().unwrap(); let index = index_manager.index.read().unwrap();
let album_key = AlbumKey {
artists: artists
.into_iter()
.filter_map(|a| index.strings.get(a))
.collect(),
name: name.and_then(|n| index.strings.get(n)),
};
index index
.collection .collection
.get_album(album_id) .get_album(&index.strings, album_key)
.ok_or_else(|| Error::AlbumNotFound) .ok_or_else(|| Error::AlbumNotFound)
} }
}) })
@ -140,7 +155,7 @@ impl Manager {
let index_manager = self.clone(); let index_manager = self.clone();
move || { move || {
let index = index_manager.index.read().unwrap(); let index = index_manager.index.read().unwrap();
Ok(index.collection.get_random_albums(count)) Ok(index.collection.get_random_albums(&index.strings, count))
} }
}) })
.await .await
@ -152,22 +167,27 @@ impl Manager {
let index_manager = self.clone(); let index_manager = self.clone();
move || { move || {
let index = index_manager.index.read().unwrap(); let index = index_manager.index.read().unwrap();
Ok(index.collection.get_recent_albums(count)) Ok(index.collection.get_recent_albums(&index.strings, count))
} }
}) })
.await .await
.unwrap() .unwrap()
} }
pub async fn get_song(&self, song_key: &SongKey) -> Result<Song, Error> { pub async fn get_song(&self, virtual_path: PathBuf) -> Result<Song, Error> {
spawn_blocking({ spawn_blocking({
let index_manager = self.clone(); let index_manager = self.clone();
let song_id = song_key.into();
move || { move || {
let index = index_manager.index.read().unwrap(); let index = index_manager.index.read().unwrap();
let Some(path_id) = virtual_path.get(&index.strings) else {
return Err(Error::SongNotFound);
};
let song_key = SongKey {
virtual_path: path_id,
};
index index
.collection .collection
.get_song(song_id) .get_song(&index.strings, song_key)
.ok_or_else(|| Error::SongNotFound) .ok_or_else(|| Error::SongNotFound)
} }
}) })
@ -209,7 +229,7 @@ impl Builder {
pub fn add_song(&mut self, song: scanner::Song) { pub fn add_song(&mut self, song: scanner::Song) {
self.browser_builder.add_song(&mut self.strings, &song); self.browser_builder.add_song(&mut self.strings, &song);
self.collection_builder.add_song(song); self.collection_builder.add_song(&mut self.strings, song);
} }
pub fn build(self) -> Index { pub fn build(self) -> Index {
@ -227,7 +247,7 @@ impl Default for Builder {
} }
} }
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub(self) struct PathID(lasso2::Spur); pub(self) struct PathID(lasso2::Spur);
pub(self) trait InternPath { pub(self) trait InternPath {

View file

@ -1,13 +1,16 @@
use std::{ use std::{
borrow::BorrowMut, borrow::BorrowMut,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
hash::{DefaultHasher, Hash, Hasher}, hash::Hash,
path::PathBuf, path::PathBuf,
}; };
use lasso2::ThreadedRodeo;
use rand::{rngs::ThreadRng, seq::IteratorRandom}; use rand::{rngs::ThreadRng, seq::IteratorRandom};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tinyvec::TinyVec;
use crate::app::index::{InternPath, PathID};
use crate::app::scanner; use crate::app::scanner;
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
@ -50,20 +53,20 @@ pub struct Song {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct Collection { pub struct Collection {
artists: HashMap<ArtistID, storage::Artist>, artists: HashMap<ArtistKey, storage::Artist>,
albums: HashMap<AlbumID, storage::Album>, albums: HashMap<AlbumKey, storage::Album>,
songs: HashMap<SongID, Song>, songs: HashMap<SongKey, storage::Song>,
recent_albums: Vec<AlbumID>, recent_albums: Vec<AlbumKey>,
} }
impl Collection { impl Collection {
pub fn get_artist(&self, artist_id: ArtistID) -> Option<Artist> { pub fn get_artist(&self, strings: &ThreadedRodeo, artist_key: ArtistKey) -> Option<Artist> {
self.artists.get(&artist_id).map(|a| { self.artists.get(&artist_key).map(|a| {
let albums = { let albums = {
let mut albums = a let mut albums = a
.albums .albums
.iter() .iter()
.filter_map(|album_id| self.get_album(*album_id)) .filter_map(|key| self.get_album(strings, key.clone()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
albums.sort_by(|a, b| (a.year, &a.name).partial_cmp(&(b.year, &b.name)).unwrap()); albums.sort_by(|a, b| (a.year, &a.name).partial_cmp(&(b.year, &b.name)).unwrap());
albums albums
@ -73,7 +76,7 @@ impl Collection {
let mut album_appearances = a let mut album_appearances = a
.album_appearances .album_appearances
.iter() .iter()
.filter_map(|album_id| self.get_album(*album_id)) .filter_map(|key| self.get_album(strings, key.clone()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
album_appearances.sort_by(|a, b| { album_appearances.sort_by(|a, b| {
(&a.artists, a.year, &a.name) (&a.artists, a.year, &a.name)
@ -84,28 +87,42 @@ impl Collection {
}; };
Artist { Artist {
name: a.name.clone(), name: a.name.map(|s| strings.resolve(&s).to_string()),
albums, albums,
album_appearances, album_appearances,
} }
}) })
} }
pub fn get_album(&self, album_id: AlbumID) -> Option<Album> { pub fn get_album(&self, strings: &ThreadedRodeo, album_key: AlbumKey) -> Option<Album> {
self.albums.get(&album_id).map(|a| { self.albums.get(&album_key).map(|a| {
let mut songs = a let mut songs = a
.songs .songs
.iter() .iter()
.filter_map(|s| self.songs.get(s)) .filter_map(|s| {
.cloned() self.get_song(
strings,
SongKey {
virtual_path: s.virtual_path,
},
)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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: a.name.clone(), name: a.name.map(|s| strings.resolve(&s).to_string()),
artwork: a.artwork.clone(), artwork: a
artists: a.artists.clone(), .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, year: a.year,
date_added: a.date_added, date_added: a.date_added,
songs, songs,
@ -113,61 +130,146 @@ impl Collection {
}) })
} }
pub fn get_random_albums(&self, count: usize) -> Vec<Album> { pub fn get_random_albums(&self, strings: &ThreadedRodeo, count: usize) -> Vec<Album> {
self.albums self.albums
.keys() .keys()
.choose_multiple(&mut ThreadRng::default(), count) .choose_multiple(&mut ThreadRng::default(), count)
.into_iter() .into_iter()
.filter_map(|k| self.get_album(*k)) .filter_map(|k| self.get_album(strings, k.clone()))
.collect() .collect()
} }
pub fn get_recent_albums(&self, count: usize) -> Vec<Album> { pub fn get_recent_albums(&self, strings: &ThreadedRodeo, count: usize) -> Vec<Album> {
self.recent_albums self.recent_albums
.iter() .iter()
.take(count) .take(count)
.filter_map(|k| self.get_album(*k)) .filter_map(|k| self.get_album(strings, k.clone()))
.collect() .collect()
} }
pub fn get_song(&self, song_id: SongID) -> Option<Song> { pub fn get_song(&self, strings: &ThreadedRodeo, song_key: SongKey) -> Option<Song> {
self.songs.get(&song_id).cloned() self.songs.get(&song_key).map(|s| Song {
path: PathBuf::from(strings.resolve(&s.path.0)),
virtual_path: PathBuf::from(strings.resolve(&s.virtual_path.0)),
virtual_parent: PathBuf::from(strings.resolve(&s.virtual_parent.0)),
track_number: s.track_number,
disc_number: s.disc_number,
title: s.title.map(|s| strings.resolve(&s).to_string()),
artists: s
.artists
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
album_artists: s
.album_artists
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
year: s.year,
album: s.album.map(|s| strings.resolve(&s).to_string()),
artwork: s.artwork.map(|a| PathBuf::from(strings.resolve(&a.0))),
duration: s.duration,
lyricists: s
.lyricists
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
composers: s
.composers
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
genres: s
.genres
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
labels: s
.labels
.iter()
.map(|s| strings.resolve(&s).to_string())
.collect(),
date_added: s.date_added,
})
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct Builder { pub struct Builder {
artists: HashMap<ArtistID, storage::Artist>, artists: HashMap<ArtistKey, storage::Artist>,
albums: HashMap<AlbumID, storage::Album>, albums: HashMap<AlbumKey, storage::Album>,
songs: HashMap<SongID, Song>, songs: HashMap<SongKey, storage::Song>,
} }
impl Builder { impl Builder {
pub fn add_song(&mut self, song: scanner::Song) { pub fn add_song(&mut self, strings: &mut ThreadedRodeo, song: scanner::Song) {
let song = Song { let Some(path_id) = song.path.get_or_intern(strings) else {
path: song.path, return;
virtual_path: song.virtual_path, };
virtual_parent: song.virtual_parent,
let Some(virtual_path_id) = song.virtual_path.get_or_intern(strings) else {
return;
};
let Some(virtual_parent_id) = song.virtual_parent.get_or_intern(strings) else {
return;
};
let Some(artwork_id) = song.artwork.map(|s| s.get_or_intern(strings)) else {
return;
};
let song = storage::Song {
path: path_id,
virtual_path: virtual_path_id,
virtual_parent: virtual_parent_id,
track_number: song.track_number, track_number: song.track_number,
disc_number: song.disc_number, disc_number: song.disc_number,
title: song.title, title: song.title.map(|s| strings.get_or_intern(s)),
artists: song.artists, artists: song
album_artists: song.album_artists, .artists
.into_iter()
.map(|s| strings.get_or_intern(s))
.collect(),
album_artists: song
.album_artists
.into_iter()
.map(|s| strings.get_or_intern(s))
.collect(),
year: song.year, year: song.year,
album: song.album, album: song.album.map(|s| strings.get_or_intern(s)),
artwork: song.artwork, artwork: artwork_id,
duration: song.duration, duration: song.duration,
lyricists: song.lyricists, lyricists: song
composers: song.composers, .lyricists
genres: song.genres, .into_iter()
labels: song.labels, .map(|s| strings.get_or_intern(s))
.collect(),
composers: song
.composers
.into_iter()
.map(|s| strings.get_or_intern(s))
.collect(),
genres: song
.genres
.into_iter()
.map(|s| strings.get_or_intern(s))
.collect(),
labels: song
.labels
.into_iter()
.map(|s| strings.get_or_intern(s))
.collect(),
date_added: song.date_added, date_added: song.date_added,
}; };
let song_id: SongID = song.song_id(); let song_key = SongKey {
virtual_path: path_id,
};
self.add_song_to_album(&song); self.add_song_to_album(&song);
self.add_album_to_artists(&song); self.add_album_to_artists(&song);
self.songs.insert(song_id, song); self.songs.insert(song_key, song);
} }
pub fn build(self) -> Collection { pub fn build(self) -> Collection {
@ -187,55 +289,50 @@ impl Builder {
} }
} }
fn add_album_to_artists(&mut self, song: &Song) { fn add_album_to_artists(&mut self, song: &storage::Song) {
let album_id: AlbumID = song.album_id(); let album_key: AlbumKey = song.album_key();
for artist_name in &song.album_artists { for artist_name in &song.album_artists {
let artist = self.get_or_create_artist(artist_name); let artist = self.get_or_create_artist(*artist_name);
artist.albums.insert(album_id); artist.albums.insert(album_key.clone());
} }
for artist_name in &song.artists { for artist_name in &song.artists {
let artist = self.get_or_create_artist(artist_name); let artist = self.get_or_create_artist(*artist_name);
if song.album_artists.is_empty() { if song.album_artists.is_empty() {
artist.albums.insert(album_id); artist.albums.insert(album_key.clone());
} else if !song.album_artists.contains(artist_name) { } else if !song.album_artists.contains(artist_name) {
artist.album_appearances.insert(album_id); artist.album_appearances.insert(album_key.clone());
} }
} }
} }
fn get_or_create_artist(&mut self, name: &String) -> &mut storage::Artist { fn get_or_create_artist(&mut self, name: lasso2::Spur) -> &mut storage::Artist {
let artist_key = ArtistKey { let artist_key = ArtistKey { name: Some(name) };
name: Some(name.clone()),
};
let artist_id: ArtistID = (&artist_key).into();
self.artists self.artists
.entry(artist_id) .entry(artist_key)
.or_insert_with(|| storage::Artist { .or_insert_with(|| storage::Artist {
name: Some(name.clone()), name: Some(name),
albums: HashSet::new(), albums: HashSet::new(),
album_appearances: HashSet::new(), album_appearances: HashSet::new(),
}) })
.borrow_mut() .borrow_mut()
} }
fn add_song_to_album(&mut self, song: &Song) { fn add_song_to_album(&mut self, song: &storage::Song) {
let song_id: SongID = song.song_id(); let album_key = song.album_key();
let album_id: AlbumID = song.album_id(); let album = self.albums.entry(album_key).or_default().borrow_mut();
let album = self.albums.entry(album_id).or_default().borrow_mut();
if album.name.is_none() { if album.name.is_none() {
album.name = song.album.clone(); album.name = song.album;
} }
if album.artwork.is_none() { if album.artwork.is_none() {
album.artwork = song.artwork.clone(); album.artwork = song.artwork;
} }
if album.year.is_none() { if album.year.is_none() {
album.year = song.year.clone(); album.year = song.year;
} }
album.date_added = album.date_added.min(song.date_added); album.date_added = album.date_added.min(song.date_added);
@ -246,7 +343,9 @@ impl Builder {
album.artists = song.artists.clone(); album.artists = song.artists.clone();
} }
album.songs.insert(song_id); album.songs.insert(SongKey {
virtual_path: song.virtual_path,
});
} }
} }
@ -255,45 +354,40 @@ mod storage {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Artist { pub struct Artist {
pub name: Option<String>, pub name: Option<lasso2::Spur>,
pub albums: HashSet<AlbumID>, pub albums: HashSet<AlbumKey>,
pub album_appearances: HashSet<AlbumID>, pub album_appearances: HashSet<AlbumKey>,
} }
#[derive(Clone, Default, Serialize, Deserialize)] #[derive(Clone, Default, Serialize, Deserialize)]
pub struct Album { pub struct Album {
pub name: Option<String>, pub name: Option<lasso2::Spur>,
pub artwork: Option<PathBuf>, pub artwork: Option<PathID>,
pub artists: Vec<String>, pub artists: Vec<lasso2::Spur>,
pub year: Option<i64>, pub year: Option<i64>,
pub date_added: i64, pub date_added: i64,
pub songs: HashSet<SongID>, pub songs: HashSet<SongKey>,
}
} }
#[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct ArtistID(u64); pub struct Song {
pub path: PathID,
#[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)] pub virtual_path: PathID,
pub struct AlbumID(u64); pub virtual_parent: PathID,
pub track_number: Option<i64>,
#[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)] pub disc_number: Option<i64>,
pub struct SongID(u64); pub title: Option<lasso2::Spur>,
pub artists: Vec<lasso2::Spur>,
#[derive(Clone, Eq, Hash, PartialEq)] pub album_artists: Vec<lasso2::Spur>,
pub struct ArtistKey { pub year: Option<i64>,
pub name: Option<String>, pub album: Option<lasso2::Spur>,
} pub artwork: Option<PathID>,
pub duration: Option<i64>,
#[derive(Clone, Eq, Hash, PartialEq)] pub lyricists: Vec<lasso2::Spur>,
pub struct AlbumKey { pub composers: Vec<lasso2::Spur>,
pub artists: Vec<String>, pub genres: Vec<lasso2::Spur>,
pub name: Option<String>, pub labels: Vec<lasso2::Spur>,
} pub date_added: i64,
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct SongKey {
pub virtual_path: PathBuf,
} }
impl Song { impl Song {
@ -308,53 +402,23 @@ impl Song {
name: self.album.clone(), name: self.album.clone(),
} }
} }
pub fn album_id(&self) -> AlbumID {
// TODO we .song_key is cloning names just so we can hash them! Slow!
let key: AlbumKey = self.album_key();
(&key).into()
}
pub fn song_key(&self) -> SongKey {
SongKey {
virtual_path: self.virtual_path.clone(),
} }
} }
pub fn song_id(&self) -> SongID { #[derive(Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
// TODO we .song_key is cloning path just so we can hash it! Slow! pub struct ArtistKey {
let key: SongKey = self.song_key(); pub name: Option<lasso2::Spur>,
(&key).into()
}
} }
impl From<&ArtistKey> for ArtistID { #[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
fn from(key: &ArtistKey) -> Self { pub struct AlbumKey {
ArtistID(key.id()) pub artists: TinyVec<[lasso2::Spur; 4]>,
} pub name: Option<lasso2::Spur>,
} }
impl From<&AlbumKey> for AlbumID { #[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
fn from(key: &AlbumKey) -> Self { pub struct SongKey {
AlbumID(key.id()) pub virtual_path: PathID,
}
}
impl From<&SongKey> for SongID {
fn from(key: &SongKey) -> Self {
SongID(key.id())
}
}
trait ID {
fn id(&self) -> u64;
}
impl<T: Hash> ID for T {
fn id(&self) -> u64 {
let mut hasher = DefaultHasher::default();
self.hash(&mut hasher);
hasher.finish()
}
} }
#[cfg(test)] #[cfg(test)]
@ -404,9 +468,7 @@ mod test {
let song = ctx let song = ctx
.index_manager .index_manager
.get_song(&SongKey { .get_song(song_virtual_path.clone())
virtual_path: song_virtual_path.clone(),
})
.await .await
.unwrap(); .unwrap();
assert_eq!(song.virtual_path, song_virtual_path); assert_eq!(song.virtual_path, song_virtual_path);

View file

@ -67,10 +67,7 @@ impl Manager {
} }
async fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble, Error> { async fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble, Error> {
let song_key = index::SongKey { let song = self.index_manager.get_song(track.to_owned()).await?;
virtual_path: track.to_owned(),
};
let song = self.index_manager.get_song(&song_key).await?;
Ok(Scrobble::new( Ok(Scrobble::new(
song.artists.first().map(|s| s.as_str()).unwrap_or(""), song.artists.first().map(|s| s.as_str()).unwrap_or(""),
song.title.as_deref().unwrap_or(""), song.title.as_deref().unwrap_or(""),

View file

@ -334,8 +334,6 @@ fn get_date_created<P: AsRef<Path>>(path: P) -> Option<i64> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*;
use std::path::PathBuf; use std::path::PathBuf;
use crate::{ use crate::{
@ -375,9 +373,7 @@ mod test {
let song = ctx let song = ctx
.index_manager .index_manager
.get_song(&index::SongKey { .get_song(song_virtual_path.clone())
virtual_path: song_virtual_path.clone(),
})
.await .await
.unwrap(); .unwrap();
assert_eq!(song.artwork, Some(song_virtual_path)); assert_eq!(song.artwork, Some(song_virtual_path));

View file

@ -284,7 +284,7 @@ fn songs_to_response(files: Vec<PathBuf>, api_version: APIMajorVersion) -> Respo
APIMajorVersion::V7 => Json( APIMajorVersion::V7 => Json(
files files
.into_iter() .into_iter()
.map(|p| index::SongKey { virtual_path: p }.into()) .map(|p| (&p).into())
.collect::<Vec<dto::v7::Song>>(), .collect::<Vec<dto::v7::Song>>(),
) )
.into_response(), .into_response(),
@ -369,10 +369,7 @@ async fn get_artist(
State(index_manager): State<index::Manager>, State(index_manager): State<index::Manager>,
Path(artist): Path<String>, Path(artist): Path<String>,
) -> Result<Json<dto::Artist>, APIError> { ) -> Result<Json<dto::Artist>, APIError> {
let artist_key = index::ArtistKey { Ok(Json(index_manager.get_artist(artist).await?.into()))
name: (!artist.is_empty()).then_some(artist),
};
Ok(Json(index_manager.get_artist(&artist_key).await?.into()))
} }
async fn get_album( async fn get_album(
@ -380,14 +377,13 @@ async fn get_album(
State(index_manager): State<index::Manager>, State(index_manager): State<index::Manager>,
Path((artists, name)): Path<(String, String)>, Path((artists, name)): Path<(String, String)>,
) -> Result<Json<dto::Album>, APIError> { ) -> Result<Json<dto::Album>, APIError> {
let album_key = index::AlbumKey { let artists = artists
artists: artists
.split(API_ARRAY_SEPARATOR) .split(API_ARRAY_SEPARATOR)
.map(str::to_owned) .map(str::to_owned)
.collect::<Vec<_>>(), .collect::<Vec<_>>();
name: (!name.is_empty()).then_some(name), Ok(Json(
}; index_manager.get_album(artists, Some(name)).await?.into(),
Ok(Json(index_manager.get_album(&album_key).await?.into())) ))
} }
async fn get_random( async fn get_random(

View file

@ -296,10 +296,10 @@ pub struct Song {
pub label: Option<String>, pub label: Option<String>,
} }
impl From<index::SongKey> for Song { impl From<&PathBuf> for Song {
fn from(song_key: index::SongKey) -> Self { fn from(path: &PathBuf) -> Self {
Self { Self {
path: song_key.virtual_path, path: path.clone(),
track_number: None, track_number: None,
disc_number: None, disc_number: None,
title: None, title: None,