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("/stream", stream::stream)
|
||||||
.at("/startScan", start_scan::start_scan)
|
.at("/startScan", start_scan::start_scan)
|
||||||
.at("/getScanStatus", get_scan_status::get_scan_status)
|
.at("/getScanStatus", get_scan_status::get_scan_status)
|
||||||
|
.at("/search3", search3::search3)
|
||||||
.boxed()
|
.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::web::{Data, Query};
|
||||||
use poem_ext::db::DbTxn;
|
use poem_ext::db::DbTxn;
|
||||||
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
authentication::Authentication,
|
authentication::Authentication,
|
||||||
scan,
|
subsonic::{
|
||||||
subsonic::{Error, SubsonicResponse},
|
Album as AlbumId3, Artist as ArtistId3, Child as ChildId3, Error, SubsonicResponse,
|
||||||
utils::{self},
|
},
|
||||||
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[poem::handler]
|
#[poem::handler]
|
||||||
|
|
@ -24,52 +32,150 @@ pub async fn search3(
|
||||||
Err(e) => return e,
|
Err(e) => return e,
|
||||||
};
|
};
|
||||||
|
|
||||||
todo!("actually implement");
|
let artists = match search_artists(txn.clone(), ¶ms).await {
|
||||||
|
Ok(v) => v,
|
||||||
let artists = vec![];
|
Err(e) => {
|
||||||
let albums = vec![];
|
error!(error = &e.root_cause(), "failed to search artists: {e}");
|
||||||
let songs = vec![];
|
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)
|
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)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Search3Params {
|
pub struct Search3Params {
|
||||||
|
#[serde(default)]
|
||||||
pub query: String,
|
pub query: String,
|
||||||
#[serde(rename = "artistCount", default = "default_artist_count")]
|
#[serde(rename = "artistCount", default = "default_artist_count")]
|
||||||
pub artist_count: i32,
|
pub artist_count: u64,
|
||||||
#[serde(rename = "artistOffset", default = "default_artist_offset")]
|
#[serde(rename = "artistOffset", default = "default_artist_offset")]
|
||||||
pub artist_offset: i32,
|
pub artist_offset: u64,
|
||||||
#[serde(rename = "albumCount", default = "default_album_count")]
|
#[serde(rename = "albumCount", default = "default_album_count")]
|
||||||
pub album_count: i32,
|
pub album_count: u64,
|
||||||
#[serde(rename = "albumOffset", default = "default_album_offset")]
|
#[serde(rename = "albumOffset", default = "default_album_offset")]
|
||||||
pub album_offset: i32,
|
pub album_offset: u64,
|
||||||
#[serde(rename = "songCount", default = "default_song_count")]
|
#[serde(rename = "songCount", default = "default_song_count")]
|
||||||
pub song_count: i32,
|
pub song_count: u64,
|
||||||
#[serde(rename = "songOffset", default = "default_song_offset")]
|
#[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
|
20
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_artist_offset() -> i32 {
|
const fn default_artist_offset() -> u64 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_album_count() -> i32 {
|
const fn default_album_count() -> u64 {
|
||||||
20
|
20
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_album_offset() -> i32 {
|
const fn default_album_offset() -> u64 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_song_count() -> i32 {
|
const fn default_song_count() -> u64 {
|
||||||
20
|
20
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_song_offset() -> i32 {
|
const fn default_song_offset() -> u64 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ mod error;
|
||||||
mod types;
|
mod types;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use types::album::Album;
|
pub use types::album::Album;
|
||||||
|
pub use types::artist::Artist;
|
||||||
pub use types::child::Child;
|
pub use types::child::Child;
|
||||||
pub use types::music_folder::MusicFolder;
|
pub use types::music_folder::MusicFolder;
|
||||||
|
|
||||||
use self::types::artist::Artist;
|
|
||||||
|
|
||||||
impl IntoResponse for SubsonicResponse {
|
impl IntoResponse for SubsonicResponse {
|
||||||
fn into_response(self) -> poem::Response {
|
fn into_response(self) -> poem::Response {
|
||||||
let body = quick_xml::se::to_string(&self).expect("Failed to serialize 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 serde::Serialize;
|
||||||
|
use time::format_description::well_known::Iso8601;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[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