feat: start work on implementing search3

Cue comes Soon. I want basic features for a client
first.
This commit is contained in:
Lys 2023-10-12 19:41:52 +03:00
parent 7a659ced4d
commit a27194816c
Signed by: lyssieth
GPG key ID: C9CF3D614FAA3940
9 changed files with 188 additions and 23 deletions

View file

@ -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
View 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
}

View file

@ -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))
} }

View file

@ -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;

View file

@ -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,
} }

View file

@ -0,0 +1,4 @@
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct Artist {}

View file

@ -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
View 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)
}

View file

@ -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()
} }