feat: start work on implementing search3
Cue comes Soon. I want basic features for a client first.
This commit is contained in:
parent
7a659ced4d
commit
a27194816c
9 changed files with 188 additions and 23 deletions
|
|
@ -16,6 +16,8 @@ mod stream;
|
||||||
mod start_scan;
|
mod start_scan;
|
||||||
// rest/getScanStatus
|
// rest/getScanStatus
|
||||||
mod get_scan_status;
|
mod get_scan_status;
|
||||||
|
// rest/search3
|
||||||
|
mod search3;
|
||||||
|
|
||||||
pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
||||||
Route::new()
|
Route::new()
|
||||||
|
|
|
||||||
75
rave/src/rest/search3.rs
Normal file
75
rave/src/rest/search3.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use poem::web::{Data, Query};
|
||||||
|
use poem_ext::db::DbTxn;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
authentication::Authentication,
|
||||||
|
scan,
|
||||||
|
subsonic::{Error, SubsonicResponse},
|
||||||
|
utils::{self},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[poem::handler]
|
||||||
|
#[instrument(skip(txn, auth))]
|
||||||
|
pub async fn search3(
|
||||||
|
Data(txn): Data<&DbTxn>,
|
||||||
|
auth: Authentication,
|
||||||
|
Query(params): Query<Search3Params>,
|
||||||
|
) -> SubsonicResponse {
|
||||||
|
let u = utils::verify_user(txn.clone(), auth).await;
|
||||||
|
|
||||||
|
match u {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
todo!("actually implement");
|
||||||
|
|
||||||
|
let artists = vec![];
|
||||||
|
let albums = vec![];
|
||||||
|
let songs = vec![];
|
||||||
|
|
||||||
|
SubsonicResponse::new_search_result3(artists, albums, songs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Search3Params {
|
||||||
|
pub query: String,
|
||||||
|
#[serde(rename = "artistCount", default = "default_artist_count")]
|
||||||
|
pub artist_count: i32,
|
||||||
|
#[serde(rename = "artistOffset", default = "default_artist_offset")]
|
||||||
|
pub artist_offset: i32,
|
||||||
|
#[serde(rename = "albumCount", default = "default_album_count")]
|
||||||
|
pub album_count: i32,
|
||||||
|
#[serde(rename = "albumOffset", default = "default_album_offset")]
|
||||||
|
pub album_offset: i32,
|
||||||
|
#[serde(rename = "songCount", default = "default_song_count")]
|
||||||
|
pub song_count: i32,
|
||||||
|
#[serde(rename = "songOffset", default = "default_song_offset")]
|
||||||
|
pub song_offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_artist_count() -> i32 {
|
||||||
|
20
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_artist_offset() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_album_count() -> i32 {
|
||||||
|
20
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_album_offset() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_song_count() -> i32 {
|
||||||
|
20
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_song_offset() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ pub async fn start_scan(Data(txn): Data<&DbTxn>, auth: Authentication) -> Subson
|
||||||
} else {
|
} else {
|
||||||
error!("Failed to start scan:");
|
error!("Failed to start scan:");
|
||||||
for e in status.errors {
|
for e in status.errors {
|
||||||
error!(error = e.root_cause(), "{e}");
|
error!(error = e.report.root_cause(), "{e:?}");
|
||||||
}
|
}
|
||||||
SubsonicResponse::new_error(Error::Generic(None))
|
SubsonicResponse::new_error(Error::Generic(None))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub async fn get_scan_status() -> Result<ScanStatus> {
|
||||||
pub struct ScanStatus {
|
pub struct ScanStatus {
|
||||||
pub scanning: bool,
|
pub scanning: bool,
|
||||||
pub count: u64,
|
pub count: u64,
|
||||||
pub errors: Vec<Arc<Report>>,
|
pub errors: Vec<ScanError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scan() {
|
async fn scan() {
|
||||||
|
|
@ -109,7 +109,10 @@ async fn do_entry(de: walk::DirEntry, txn: DatabaseTransaction, state: Arc<RwLoc
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut write = STATUS.write().await;
|
let mut write = STATUS.write().await;
|
||||||
write.errors.push(Arc::new(e));
|
write.errors.push(ScanError {
|
||||||
|
report: Arc::new(e),
|
||||||
|
additional: path.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -140,13 +143,12 @@ async fn handle_entry(
|
||||||
error!("Couldn't get file extension for {path:?}");
|
error!("Couldn't get file extension for {path:?}");
|
||||||
|
|
||||||
{
|
{
|
||||||
STATUS
|
STATUS.write().await.errors.push(ScanError {
|
||||||
.write()
|
report: Arc::new(Report::msg(format!(
|
||||||
.await
|
|
||||||
.errors
|
|
||||||
.push(Arc::new(Report::msg(format!(
|
|
||||||
"Couldn't get file extension for {path:?}"
|
"Couldn't get file extension for {path:?}"
|
||||||
))));
|
))),
|
||||||
|
additional: path.to_string_lossy().to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -164,13 +166,10 @@ async fn handle_entry(
|
||||||
error!("Couldn't get file stem for {path:?}");
|
error!("Couldn't get file stem for {path:?}");
|
||||||
|
|
||||||
{
|
{
|
||||||
STATUS
|
STATUS.write().await.errors.push(ScanError {
|
||||||
.write()
|
report: Arc::new(Report::msg(format!("Couldn't get file stem for {path:?}"))),
|
||||||
.await
|
additional: path.to_string_lossy().to_string(),
|
||||||
.errors
|
});
|
||||||
.push(Arc::new(Report::msg(format!(
|
|
||||||
"Couldn't get file stem for {path:?}"
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -233,6 +232,12 @@ pub struct ScanState {
|
||||||
pub music_folder_id: i64,
|
pub music_folder_id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ScanError {
|
||||||
|
pub report: Arc<Report>,
|
||||||
|
pub additional: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(dbc, state))]
|
#[instrument(skip(dbc, state))]
|
||||||
async fn create_root_music_folder(
|
async fn create_root_music_folder(
|
||||||
dbc: &sea_orm::DatabaseConnection,
|
dbc: &sea_orm::DatabaseConnection,
|
||||||
|
|
@ -250,7 +255,10 @@ async fn create_root_music_folder(
|
||||||
{
|
{
|
||||||
let mut stat = STATUS.write().await;
|
let mut stat = STATUS.write().await;
|
||||||
stat.scanning = false;
|
stat.scanning = false;
|
||||||
stat.errors.push(Arc::new(Report::new(err)));
|
stat.errors.push(ScanError {
|
||||||
|
report: Arc::new(Report::new(err)),
|
||||||
|
additional: root_dir.to_string_lossy().to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return ControlFlow::Break(());
|
return ControlFlow::Break(());
|
||||||
};
|
};
|
||||||
|
|
@ -290,7 +298,10 @@ async fn create_root_music_folder(
|
||||||
{
|
{
|
||||||
let mut stat = STATUS.write().await;
|
let mut stat = STATUS.write().await;
|
||||||
stat.scanning = false;
|
stat.scanning = false;
|
||||||
stat.errors.push(Arc::new(Report::new(err)));
|
stat.errors.push(ScanError {
|
||||||
|
report: Arc::new(Report::new(err)),
|
||||||
|
additional: root_dir.to_string_lossy().to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = txn.rollback().await;
|
let _ = txn.rollback().await;
|
||||||
|
|
@ -314,7 +325,10 @@ async fn create_txn(dbc: &sea_orm::DatabaseConnection) -> Option<DatabaseTransac
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut write = STATUS.write().await;
|
let mut write = STATUS.write().await;
|
||||||
write.errors.push(Arc::new(Report::new(e)));
|
write.errors.push(ScanError {
|
||||||
|
report: Arc::new(Report::new(e)),
|
||||||
|
additional: String::new(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -336,7 +350,10 @@ async fn check_dir_entry(
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut write = STATUS.write().await;
|
let mut write = STATUS.write().await;
|
||||||
write.errors.push(Arc::new(Report::new(e)));
|
write.errors.push(ScanError {
|
||||||
|
report: Arc::new(Report::new(e)),
|
||||||
|
additional: String::new(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -357,8 +374,10 @@ async fn get_dbc(conn: ConnectOptions) -> Option<sea_orm::DatabaseConnection> {
|
||||||
{
|
{
|
||||||
let mut stat = STATUS.write().await;
|
let mut stat = STATUS.write().await;
|
||||||
stat.scanning = false;
|
stat.scanning = false;
|
||||||
stat.errors
|
stat.errors.push(ScanError {
|
||||||
.push(Arc::new(Report::msg("Failed to connect to database")));
|
report: Arc::new(Report::new(e)),
|
||||||
|
additional: String::new(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ pub use types::album::Album;
|
||||||
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");
|
||||||
|
|
@ -60,6 +62,14 @@ impl SubsonicResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_search_result3(artists: Vec<Artist>, albums: Vec<Album>, songs: Vec<Child>) -> Self {
|
||||||
|
Self::new(SubResponseType::SearchResult3 {
|
||||||
|
artists,
|
||||||
|
albums,
|
||||||
|
songs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_empty() -> Self {
|
pub fn new_empty() -> Self {
|
||||||
Self::new(SubResponseType::Empty)
|
Self::new(SubResponseType::Empty)
|
||||||
}
|
}
|
||||||
|
|
@ -121,6 +131,15 @@ pub enum SubResponseType {
|
||||||
#[serde(rename = "count")]
|
#[serde(rename = "count")]
|
||||||
count: u64,
|
count: u64,
|
||||||
},
|
},
|
||||||
|
#[serde(rename = "searchResult3")]
|
||||||
|
SearchResult3 {
|
||||||
|
#[serde(rename = "artist")]
|
||||||
|
artists: Vec<Artist>,
|
||||||
|
#[serde(rename = "album")]
|
||||||
|
albums: Vec<Album>,
|
||||||
|
#[serde(rename = "song")]
|
||||||
|
songs: Vec<Child>,
|
||||||
|
},
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4
rave/src/subsonic/types/artist.rs
Normal file
4
rave/src/subsonic/types/artist.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct Artist {}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod album;
|
pub mod album;
|
||||||
|
pub mod artist;
|
||||||
pub mod child;
|
pub mod child;
|
||||||
pub mod music_folder;
|
pub mod music_folder;
|
||||||
43
rave/src/ui/errors.rs
Normal file
43
rave/src/ui/errors.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use poem::{http::StatusCode, Response};
|
||||||
|
|
||||||
|
use crate::scan;
|
||||||
|
|
||||||
|
#[poem::handler]
|
||||||
|
pub async fn errors() -> Response {
|
||||||
|
let scan_status = scan::get_scan_status().await;
|
||||||
|
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
body.push_str("<html><head><title>Rave</title></head><body>");
|
||||||
|
|
||||||
|
body.push_str("<h1>Errors</h1>");
|
||||||
|
|
||||||
|
match scan_status {
|
||||||
|
Ok(status) => {
|
||||||
|
body.push_str(&format!(
|
||||||
|
"<h2>Scan status: {} ({})</h2>",
|
||||||
|
status.scanning, status.count
|
||||||
|
));
|
||||||
|
|
||||||
|
body.push_str("<ul>");
|
||||||
|
for error in status.errors {
|
||||||
|
body.push_str(&format!(
|
||||||
|
"<li>{}: <code>{}</code></li>",
|
||||||
|
error.additional, error.report
|
||||||
|
));
|
||||||
|
}
|
||||||
|
body.push_str("</ul>");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
body.push_str("<h2>Error</h2>");
|
||||||
|
body.push_str(&format!("<p>{e}</p>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.push_str("</body></html>");
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header("Content-Type", "text/html")
|
||||||
|
.body(body)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use poem::{Endpoint, EndpointExt, Route};
|
use poem::{Endpoint, EndpointExt, Route};
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
|
||||||
pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
||||||
Route::new().boxed()
|
Route::new().at("/errors", errors::errors).boxed()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue