feat: implement searching
This commit is contained in:
parent
a27194816c
commit
07197a6703
4 changed files with 159 additions and 23 deletions
|
|
@ -30,5 +30,6 @@ pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
|||
.at("/stream", stream::stream)
|
||||
.at("/startScan", start_scan::start_scan)
|
||||
.at("/getScanStatus", get_scan_status::get_scan_status)
|
||||
.at("/search3", search3::search3)
|
||||
.boxed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
use color_eyre::Report;
|
||||
use entities::{
|
||||
album, artist,
|
||||
prelude::{Album, Artist, Track},
|
||||
track,
|
||||
};
|
||||
use poem::web::{Data, Query};
|
||||
use poem_ext::db::DbTxn;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect};
|
||||
use serde::Deserialize;
|
||||
use tracing::{error, instrument};
|
||||
|
||||
use crate::{
|
||||
authentication::Authentication,
|
||||
scan,
|
||||
subsonic::{Error, SubsonicResponse},
|
||||
utils::{self},
|
||||
subsonic::{
|
||||
Album as AlbumId3, Artist as ArtistId3, Child as ChildId3, Error, SubsonicResponse,
|
||||
},
|
||||
utils,
|
||||
};
|
||||
|
||||
#[poem::handler]
|
||||
|
|
@ -24,52 +32,150 @@ pub async fn search3(
|
|||
Err(e) => return e,
|
||||
};
|
||||
|
||||
todo!("actually implement");
|
||||
|
||||
let artists = vec![];
|
||||
let albums = vec![];
|
||||
let songs = vec![];
|
||||
let artists = match search_artists(txn.clone(), ¶ms).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(error = &e.root_cause(), "failed to search artists: {e}");
|
||||
return SubsonicResponse::new_error(Error::Generic(None));
|
||||
}
|
||||
};
|
||||
let albums = match search_albums(txn.clone(), ¶ms).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(error = &e.root_cause(), "failed to search albums: {e}");
|
||||
return SubsonicResponse::new_error(Error::Generic(None));
|
||||
}
|
||||
};
|
||||
let songs = match search_songs(txn.clone(), ¶ms).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(error = &e.root_cause(), "failed to search songs: {e}");
|
||||
return SubsonicResponse::new_error(Error::Generic(None));
|
||||
}
|
||||
};
|
||||
|
||||
SubsonicResponse::new_search_result3(artists, albums, songs)
|
||||
}
|
||||
|
||||
async fn search_artists(txn: DbTxn, params: &Search3Params) -> Result<Vec<ArtistId3>, Report> {
|
||||
if params.query.is_empty() {
|
||||
let artists = Artist::find()
|
||||
.limit(params.artist_count)
|
||||
.offset(params.artist_offset)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(artists.into_iter().map(Into::into).collect())
|
||||
} else {
|
||||
let artists = Artist::find()
|
||||
.filter(artist::Column::Name.contains(¶ms.query))
|
||||
.limit(params.artist_count)
|
||||
.offset(params.artist_offset)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(artists.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
async fn search_albums(txn: DbTxn, params: &Search3Params) -> Result<Vec<AlbumId3>, Report> {
|
||||
if params.query.is_empty() {
|
||||
let albums = Album::find()
|
||||
.limit(params.album_count)
|
||||
.offset(params.album_offset)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(albums
|
||||
.into_iter()
|
||||
.map(|v| AlbumId3::new(v, None, None))
|
||||
.collect())
|
||||
} else {
|
||||
let albums = Album::find()
|
||||
.filter(album::Column::Name.contains(¶ms.query))
|
||||
.limit(params.album_count)
|
||||
.offset(params.album_offset)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(albums
|
||||
.into_iter()
|
||||
.map(|v| AlbumId3::new(v, None, None))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
async fn search_songs(txn: DbTxn, params: &Search3Params) -> Result<Vec<ChildId3>, Report> {
|
||||
if params.query.is_empty() {
|
||||
let songs = Track::find()
|
||||
.limit(params.song_count)
|
||||
.offset(params.song_offset)
|
||||
.find_also_related(Album)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(songs
|
||||
.into_iter()
|
||||
.filter_map(|(track, album)| {
|
||||
album.map(|album| ChildId3::new(&track, &AlbumId3::new(album, None, None)))
|
||||
})
|
||||
.collect())
|
||||
} else {
|
||||
let songs = Track::find()
|
||||
.filter(track::Column::Title.contains(¶ms.query))
|
||||
.limit(params.song_count)
|
||||
.offset(params.song_offset)
|
||||
.find_also_related(Album)
|
||||
.all(&*txn)
|
||||
.await?;
|
||||
|
||||
Ok(songs
|
||||
.into_iter()
|
||||
.filter_map(|(track, album)| {
|
||||
album.map(|album| ChildId3::new(&track, &AlbumId3::new(album, None, None)))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Search3Params {
|
||||
#[serde(default)]
|
||||
pub query: String,
|
||||
#[serde(rename = "artistCount", default = "default_artist_count")]
|
||||
pub artist_count: i32,
|
||||
pub artist_count: u64,
|
||||
#[serde(rename = "artistOffset", default = "default_artist_offset")]
|
||||
pub artist_offset: i32,
|
||||
pub artist_offset: u64,
|
||||
#[serde(rename = "albumCount", default = "default_album_count")]
|
||||
pub album_count: i32,
|
||||
pub album_count: u64,
|
||||
#[serde(rename = "albumOffset", default = "default_album_offset")]
|
||||
pub album_offset: i32,
|
||||
pub album_offset: u64,
|
||||
#[serde(rename = "songCount", default = "default_song_count")]
|
||||
pub song_count: i32,
|
||||
pub song_count: u64,
|
||||
#[serde(rename = "songOffset", default = "default_song_offset")]
|
||||
pub song_offset: i32,
|
||||
pub song_offset: u64,
|
||||
}
|
||||
|
||||
const fn default_artist_count() -> i32 {
|
||||
const fn default_artist_count() -> u64 {
|
||||
20
|
||||
}
|
||||
|
||||
const fn default_artist_offset() -> i32 {
|
||||
const fn default_artist_offset() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
const fn default_album_count() -> i32 {
|
||||
const fn default_album_count() -> u64 {
|
||||
20
|
||||
}
|
||||
|
||||
const fn default_album_offset() -> i32 {
|
||||
const fn default_album_offset() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
const fn default_song_count() -> i32 {
|
||||
const fn default_song_count() -> u64 {
|
||||
20
|
||||
}
|
||||
|
||||
const fn default_song_offset() -> i32 {
|
||||
const fn default_song_offset() -> u64 {
|
||||
0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ mod error;
|
|||
mod types;
|
||||
pub use error::Error;
|
||||
pub use types::album::Album;
|
||||
pub use types::artist::Artist;
|
||||
pub use types::child::Child;
|
||||
pub use types::music_folder::MusicFolder;
|
||||
|
||||
use self::types::artist::Artist;
|
||||
|
||||
impl IntoResponse for SubsonicResponse {
|
||||
fn into_response(self) -> poem::Response {
|
||||
let body = quick_xml::se::to_string(&self).expect("Failed to serialize response");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,34 @@
|
|||
use entities::artist;
|
||||
use serde::Serialize;
|
||||
use time::format_description::well_known::Iso8601;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Artist {}
|
||||
pub struct Artist {
|
||||
#[serde(rename = "@id")]
|
||||
pub id: String,
|
||||
#[serde(rename = "@name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "@coverArtId")]
|
||||
pub cover_art: Option<String>,
|
||||
#[serde(rename = "@artistImageUrl")]
|
||||
pub artist_image_url: Option<String>,
|
||||
#[serde(rename = "@albumCount")]
|
||||
pub album_count: u64,
|
||||
#[serde(rename = "@starred")]
|
||||
pub starred: Option<String>,
|
||||
}
|
||||
|
||||
impl From<artist::Model> for Artist {
|
||||
fn from(artist: artist::Model) -> Self {
|
||||
Self {
|
||||
id: format!("ar-{}", artist.id),
|
||||
name: artist.name,
|
||||
cover_art: artist.cover_art_id.map(|v| format!("ca-{}", v)),
|
||||
artist_image_url: artist.artist_image_url,
|
||||
album_count: artist.album_count as u64,
|
||||
starred: artist
|
||||
.starred
|
||||
.map(|v| v.format(&Iso8601::DEFAULT).expect("failed to format date")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue