From b8416b29dd72908e849306cb184314f7be93b7e8 Mon Sep 17 00:00:00 2001 From: Sijmen Date: Thu, 13 Jul 2023 00:35:54 +0200 Subject: [PATCH] Split routes up into smaller modules --- src/crate_version.rs | 6 ++ src/main.rs | 216 ++---------------------------------------- src/routes/art.rs | 40 ++++++++ src/routes/browser.rs | 34 +++++++ src/routes/index.rs | 17 ++++ src/routes/mod.rs | 5 + src/routes/player.rs | 43 +++++++++ src/routes/queue.rs | 82 ++++++++++++++++ 8 files changed, 237 insertions(+), 206 deletions(-) create mode 100644 src/crate_version.rs create mode 100644 src/routes/art.rs create mode 100644 src/routes/browser.rs create mode 100644 src/routes/index.rs create mode 100644 src/routes/mod.rs create mode 100644 src/routes/player.rs create mode 100644 src/routes/queue.rs diff --git a/src/crate_version.rs b/src/crate_version.rs new file mode 100644 index 0000000..7ce0272 --- /dev/null +++ b/src/crate_version.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! crate_version { + () => { + env!("CARGO_PKG_VERSION") + }; +} diff --git a/src/main.rs b/src/main.rs index 3d0a21e..e9d74af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,157 +1,6 @@ -use std::{collections::HashMap, path::Path}; - -use askama::Template; -use percent_encoding::percent_decode_str; -use serde::Deserialize; - mod mpd; - -macro_rules! crate_version { - () => { - env!("CARGO_PKG_VERSION") - }; -} - -#[derive(Template)] -#[template(path = "index.html")] -struct IndexTemplate; - -#[derive(Deserialize, Default)] -#[serde(default)] -struct IndexQuery { - path: String, -} - -async fn get_index(_req: tide::Request<()>) -> tide::Result { - Ok(askama_tide::into_response(&IndexTemplate)) -} - -#[derive(Template)] -#[template(path = "queue.html")] -struct QueueTemplate { - queue: Vec, -} - -async fn get_queue(_req: tide::Request<()>) -> tide::Result { - let queue = mpd::Mpd::connect().await?.playlist().await?; - let template = QueueTemplate { queue }; - Ok(template.into()) -} - -#[derive(Template)] -#[template(path = "player.html")] -struct PlayerTemplate<'a> { - song: Option<&'a HashMap>, - name: Option, - state: &'a str, - elapsed: f32, - duration: f32, -} - -async fn get_player(_req: tide::Request<()>) -> tide::Result { - let mut mpd = mpd::Mpd::connect().await?; - let song = mpd.command("currentsong").await?.into_hashmap(); - let status = mpd.command("status").await?.into_hashmap(); - - let elapsed = status - .get("elapsed") - .and_then(|e| e.parse().ok()) - .unwrap_or(0.0); - let duration = status - .get("duration") - .and_then(|e| e.parse().ok()) - .unwrap_or(1.0); - - let mut template = PlayerTemplate { - song: if song.is_empty() { None } else { Some(&song) }, - name: None, - state: &status["state"], - elapsed, - duration, - }; - - if !song.is_empty() { - let name = song.get("Title").unwrap_or(&song["file"]).to_string(); - template.name = Some(name); - } - - Ok(template.into()) -} - -#[derive(Template)] -#[template(path = "browser.html")] -struct BrowserTemplate { - path: Vec, - entries: Vec, -} - -async fn get_browser(req: tide::Request<()>) -> tide::Result { - let query: IndexQuery = req.query()?; - let path = percent_decode_str(&query.path).decode_utf8_lossy(); - let entries = mpd::Mpd::connect().await?.ls(&path).await?; - - let template = BrowserTemplate { - path: Path::new(&*path) - .iter() - .map(|s| s.to_string_lossy().to_string()) - .collect(), - entries, - }; - - Ok(template.into()) -} - -#[derive(Deserialize)] -struct PostQueueQuery { - path: String, - #[serde(default)] - replace: bool, - #[serde(default)] - next: bool, - #[serde(default)] - play: bool, -} - -async fn post_queue(req: tide::Request<()>) -> tide::Result { - let query: PostQueueQuery = req.query()?; - let path = percent_decode_str(&query.path).decode_utf8_lossy(); - let mut mpd = mpd::Mpd::connect().await?; - - if query.replace { - mpd.clear().await?; - } - - if query.next { - mpd.add_pos(&path, "+0").await?; - } else { - mpd.add(&path).await?; - } - - if query.play { - mpd.play().await?; - } - - Ok("".into()) -} - -#[derive(Deserialize)] -struct DeleteQueueQuery { - #[serde(default)] - id: Option, -} - -async fn delete_queue(req: tide::Request<()>) -> tide::Result { - let query: DeleteQueueQuery = req.query()?; - - let mut mpd = mpd::Mpd::connect().await?; - if let Some(id) = query.id { - mpd.command(&format!("deleteid {id}")).await?; - } else { - mpd.command("clear").await?; - } - - Ok("".into()) -} +mod routes; +mod crate_version; async fn post_play(_req: tide::Request<()>) -> tide::Result { mpd::Mpd::connect().await?.command("play").await?; @@ -173,51 +22,6 @@ async fn post_next(_req: tide::Request<()>) -> tide::Result { Ok("".into()) } -#[derive(Deserialize, Debug)] -struct UpdateQueueBody { - from: u32, - to: u32, -} - -async fn post_queue_move(mut req: tide::Request<()>) -> tide::Result { - let body: UpdateQueueBody = req.body_json().await?; - let mut mpd = mpd::Mpd::connect().await?; - mpd.command(&format!("move {} {}", body.from, body.to)) - .await?; - Ok("".into()) -} - -async fn get_art(req: tide::Request<()>) -> tide::Result { - let query: IndexQuery = req.query()?; - let path = percent_decode_str(&query.path).decode_utf8_lossy(); - - let mut mpd = mpd::Mpd::connect().await?; - - let resp = if let Ok(art) = mpd.albumart(&path).await { - let mime = infer::get(&art) - .map(|k| k.mime_type()) - .unwrap_or("application/octet-stream"); - - tide::Response::builder(tide::StatusCode::Ok) - .body(art) - .content_type(mime) - .header("cache-control", "max-age=3600") - } else if let Ok(art) = mpd.readpicture(&path).await { - let mime = infer::get(&art) - .map(|k| k.mime_type()) - .unwrap_or("application/octet-stream"); - - tide::Response::builder(tide::StatusCode::Ok) - .body(art) - .content_type(mime) - .header("cache-control", "max-age=3600") - } else { - tide::Response::builder(tide::StatusCode::NotFound) - }; - - Ok(resp.into()) -} - async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> { // Update everything on connect sender.send("playlist", "", None).await?; @@ -242,17 +46,17 @@ async fn main() -> tide::Result<()> { let mut app = tide::new(); app.with(tide_tracing::TraceMiddleware::new()); - app.at("/").get(get_index); - app.at("/queue").get(get_queue); - app.at("/player").get(get_player); - app.at("/art").get(get_art); - app.at("/browser").get(get_browser); + app.at("/").get(routes::index::get_index); + app.at("/player").get(routes::player::get_player); + app.at("/browser").get(routes::browser::get_browser); + app.at("/art").get(routes::art::get_art); app.at("/sse").get(tide::sse::endpoint(sse)); - app.at("/queue").post(post_queue); - app.at("/queue").delete(delete_queue); - app.at("/queue/move").post(post_queue_move); + app.at("/queue").get(routes::queue::get_queue); + app.at("/queue").post(routes::queue::post_queue); + app.at("/queue").delete(routes::queue::delete_queue); + app.at("/queue/move").post(routes::queue::post_queue_move); app.at("/play").post(post_play); app.at("/pause").post(post_pause); diff --git a/src/routes/art.rs b/src/routes/art.rs new file mode 100644 index 0000000..0cbfb62 --- /dev/null +++ b/src/routes/art.rs @@ -0,0 +1,40 @@ +use serde::Deserialize; +use crate::mpd; +use percent_encoding::percent_decode_str; + +#[derive(Deserialize, Default)] +#[serde(default)] +struct ArtQuery { + path: String, +} + +pub async fn get_art(req: tide::Request<()>) -> tide::Result { + let query: ArtQuery = req.query()?; + let path = percent_decode_str(&query.path).decode_utf8_lossy(); + + let mut mpd = mpd::Mpd::connect().await?; + + let resp = if let Ok(art) = mpd.albumart(&path).await { + let mime = infer::get(&art) + .map(|k| k.mime_type()) + .unwrap_or("application/octet-stream"); + + tide::Response::builder(tide::StatusCode::Ok) + .body(art) + .content_type(mime) + .header("cache-control", "max-age=3600") + } else if let Ok(art) = mpd.readpicture(&path).await { + let mime = infer::get(&art) + .map(|k| k.mime_type()) + .unwrap_or("application/octet-stream"); + + tide::Response::builder(tide::StatusCode::Ok) + .body(art) + .content_type(mime) + .header("cache-control", "max-age=3600") + } else { + tide::Response::builder(tide::StatusCode::NotFound) + }; + + Ok(resp.into()) +} diff --git a/src/routes/browser.rs b/src/routes/browser.rs new file mode 100644 index 0000000..1316a96 --- /dev/null +++ b/src/routes/browser.rs @@ -0,0 +1,34 @@ +use askama::Template; +use serde::Deserialize; +use crate::mpd; +use percent_encoding::percent_decode_str; +use std::path::Path; + +#[derive(Template)] +#[template(path = "browser.html")] +struct BrowserTemplate { + path: Vec, + entries: Vec, +} + +#[derive(Deserialize, Default)] +#[serde(default)] +struct BrowserQuery { + path: String, +} + +pub async fn get_browser(req: tide::Request<()>) -> tide::Result { + let query: BrowserQuery = req.query()?; + let path = percent_decode_str(&query.path).decode_utf8_lossy(); + let entries = mpd::Mpd::connect().await?.ls(&path).await?; + + let template = BrowserTemplate { + path: Path::new(&*path) + .iter() + .map(|s| s.to_string_lossy().to_string()) + .collect(), + entries, + }; + + Ok(template.into()) +} diff --git a/src/routes/index.rs b/src/routes/index.rs new file mode 100644 index 0000000..3207567 --- /dev/null +++ b/src/routes/index.rs @@ -0,0 +1,17 @@ +use askama::Template; +use serde::Deserialize; +use crate::crate_version; + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate; + +#[derive(Deserialize, Default)] +#[serde(default)] +struct IndexQuery { + path: String, +} + +pub async fn get_index(_req: tide::Request<()>) -> tide::Result { + Ok(askama_tide::into_response(&IndexTemplate)) +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..535081b --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,5 @@ +pub mod index; +pub mod queue; +pub mod player; +pub mod browser; +pub mod art; \ No newline at end of file diff --git a/src/routes/player.rs b/src/routes/player.rs new file mode 100644 index 0000000..0b1ac74 --- /dev/null +++ b/src/routes/player.rs @@ -0,0 +1,43 @@ +use askama::Template; +use crate::mpd; +use std::collections::HashMap; + +#[derive(Template)] +#[template(path = "player.html")] +struct PlayerTemplate<'a> { + song: Option<&'a HashMap>, + name: Option, + state: &'a str, + elapsed: f32, + duration: f32, +} + +pub async fn get_player(_req: tide::Request<()>) -> tide::Result { + let mut mpd = mpd::Mpd::connect().await?; + let song = mpd.command("currentsong").await?.into_hashmap(); + let status = mpd.command("status").await?.into_hashmap(); + + let elapsed = status + .get("elapsed") + .and_then(|e| e.parse().ok()) + .unwrap_or(0.0); + let duration = status + .get("duration") + .and_then(|e| e.parse().ok()) + .unwrap_or(1.0); + + let mut template = PlayerTemplate { + song: if song.is_empty() { None } else { Some(&song) }, + name: None, + state: &status["state"], + elapsed, + duration, + }; + + if !song.is_empty() { + let name = song.get("Title").unwrap_or(&song["file"]).to_string(); + template.name = Some(name); + } + + Ok(template.into()) +} diff --git a/src/routes/queue.rs b/src/routes/queue.rs new file mode 100644 index 0000000..26dcc5d --- /dev/null +++ b/src/routes/queue.rs @@ -0,0 +1,82 @@ +use askama::Template; +use crate::mpd; +use serde::Deserialize; +use percent_encoding::percent_decode_str; + +#[derive(Template)] +#[template(path = "queue.html")] +struct QueueTemplate { + queue: Vec, +} + +pub async fn get_queue(_req: tide::Request<()>) -> tide::Result { + let queue = mpd::Mpd::connect().await?.playlist().await?; + let template = QueueTemplate { queue }; + Ok(template.into()) +} + +#[derive(Deserialize)] +struct PostQueueQuery { + path: String, + #[serde(default)] + replace: bool, + #[serde(default)] + next: bool, + #[serde(default)] + play: bool, +} + +pub async fn post_queue(req: tide::Request<()>) -> tide::Result { + let query: PostQueueQuery = req.query()?; + let path = percent_decode_str(&query.path).decode_utf8_lossy(); + let mut mpd = mpd::Mpd::connect().await?; + + if query.replace { + mpd.clear().await?; + } + + if query.next { + mpd.add_pos(&path, "+0").await?; + } else { + mpd.add(&path).await?; + } + + if query.play { + mpd.play().await?; + } + + Ok("".into()) +} + +#[derive(Deserialize)] +struct DeleteQueueQuery { + #[serde(default)] + id: Option, +} + +pub async fn delete_queue(req: tide::Request<()>) -> tide::Result { + let query: DeleteQueueQuery = req.query()?; + + let mut mpd = mpd::Mpd::connect().await?; + if let Some(id) = query.id { + mpd.command(&format!("deleteid {id}")).await?; + } else { + mpd.command("clear").await?; + } + + Ok("".into()) +} + +#[derive(Deserialize, Debug)] +struct UpdateQueueBody { + from: u32, + to: u32, +} + +pub async fn post_queue_move(mut req: tide::Request<()>) -> tide::Result { + let body: UpdateQueueBody = req.body_json().await?; + let mut mpd = mpd::Mpd::connect().await?; + mpd.command(&format!("move {} {}", body.from, body.to)) + .await?; + Ok("".into()) +}